From 0a2e63e6ebd8bc1a363d2b6b9efd3f48681e5414 Mon Sep 17 00:00:00 2001 From: Dominique Barton Date: Tue, 14 Apr 2020 22:05:32 +0200 Subject: [PATCH 1/3] FEATURE: Add next & previous track actions Also refactor the code to get rid of separate action functions & tag classes. Now there's only action classes. See also #13 --- README.rst | 7 +- mopidy_pummeluff/__init__.py | 4 +- mopidy_pummeluff/actions.py | 88 ------------------- .../{tags => actions}/__init__.py | 17 ++-- mopidy_pummeluff/{tags => actions}/base.py | 44 +++++----- mopidy_pummeluff/actions/playback.py | 86 ++++++++++++++++++ mopidy_pummeluff/actions/shutdown.py | 30 +++++++ mopidy_pummeluff/actions/tracklist.py | 41 +++++++++ mopidy_pummeluff/{tags => actions}/volume.py | 24 ++++- mopidy_pummeluff/frontend.py | 2 +- mopidy_pummeluff/registry.py | 48 +++++----- mopidy_pummeluff/tags/play_pause.py | 18 ---- mopidy_pummeluff/tags/shutdown.py | 18 ---- mopidy_pummeluff/tags/stop.py | 18 ---- mopidy_pummeluff/tags/tracklist.py | 19 ---- mopidy_pummeluff/threads/gpio_handler.py | 11 ++- mopidy_pummeluff/threads/tag_reader.py | 12 +-- mopidy_pummeluff/web.py | 14 +-- mopidy_pummeluff/webui/index.html | 4 +- mopidy_pummeluff/webui/script.js | 26 +++--- mopidy_pummeluff/webui/style.css | 4 +- 21 files changed, 278 insertions(+), 257 deletions(-) delete mode 100644 mopidy_pummeluff/actions.py rename mopidy_pummeluff/{tags => actions}/__init__.py (54%) rename mopidy_pummeluff/{tags => actions}/base.py (74%) create mode 100644 mopidy_pummeluff/actions/playback.py create mode 100644 mopidy_pummeluff/actions/shutdown.py create mode 100644 mopidy_pummeluff/actions/tracklist.py rename mopidy_pummeluff/{tags => actions}/volume.py (53%) delete mode 100644 mopidy_pummeluff/tags/play_pause.py delete mode 100644 mopidy_pummeluff/tags/shutdown.py delete mode 100644 mopidy_pummeluff/tags/stop.py delete mode 100644 mopidy_pummeluff/tags/tracklist.py diff --git a/README.rst b/README.rst index 7cb55d0..83bcfb5 100644 --- a/README.rst +++ b/README.rst @@ -62,9 +62,12 @@ Connecting the buttons (optional) You can connect two buttons to the RPi: - ``RPi pin 5`` - Power button: Shutdown the Raspberry Pi into halt state & wake it up again from halt state -- ``RPi pin 7`` - Playback button: Pause and resume the playback +- ``RPi pin 29`` - Playback button: Pause and resume the playback +- ``RPi pin 31`` - Stop button: Stops the playback +- ``RPi pin 33`` - Previous button: Changes to the previous track in the playlist +- ``RPi pin 35`` - Next button: Changes to the next track in the playlist -The buttons must shortcut their corresponding pins against ``GND`` (e.g. pin ``6``) when pressed. This means you want to connect one pin of the button (i.e. ``C``) to RPI's ``GND``, and the other one (i.e. ``NO``) to RPi's pin ``5`` or ``7``. +The buttons must shortcut their corresponding pins against ``GND`` (e.g. pin ``6``) when pressed. This means you want to connect one pin of the button (i.e. ``C``) to RPI's ``GND``, and the other one (i.e. ``NO``) to RPi's pin ``5``, ``29``, ``31``, ``33`` or ``35``. Connecting the status LED (optional) ------------------------------------ diff --git a/mopidy_pummeluff/__init__.py b/mopidy_pummeluff/__init__.py index fde895c..09ce374 100644 --- a/mopidy_pummeluff/__init__.py +++ b/mopidy_pummeluff/__init__.py @@ -7,7 +7,7 @@ import mopidy from .frontend import PummeluffFrontend -from .web import LatestHandler, RegistryHandler, RegisterHandler, TagClassesHandler +from .web import LatestHandler, RegistryHandler, RegisterHandler, ActionClassesHandler def app_factory(config, core): # pylint: disable=unused-argument @@ -24,7 +24,7 @@ def app_factory(config, core): # pylint: disable=unused-argument ('/latest/', LatestHandler, {'core': core}), ('/registry/', RegistryHandler, {'core': core}), ('/register/', RegisterHandler, {'core': core}), - ('/tag-classes/', TagClassesHandler, {'core': core}), + ('/action-classes/', ActionClassesHandler, {'core': core}), ] diff --git a/mopidy_pummeluff/actions.py b/mopidy_pummeluff/actions.py deleted file mode 100644 index 43e478a..0000000 --- a/mopidy_pummeluff/actions.py +++ /dev/null @@ -1,88 +0,0 @@ -''' -Python module for Mopidy Pummeluff actions. -''' - -__all__ = ( - 'replace_tracklist', - 'set_volume', - 'play_pause', - 'stop', - 'shutdown', -) - -from logging import getLogger -from os import system - -LOGGER = getLogger(__name__) - - -def replace_tracklist(core, uri): - ''' - Replace tracklist and play. - - :param mopidy.core.Core core: The mopidy core instance - :param str uri: An URI for the tracklist replacement - ''' - LOGGER.info('Replacing tracklist with URI "%s"', uri) - - playlists = [playlist.uri for playlist in core.playlists.as_list().get()] - - if uri in playlists: - uris = [item.uri for item in core.playlists.get_items(uri).get()] - else: - uris = [uri] - - core.tracklist.clear() - core.tracklist.add(uris=uris) - core.playback.play() - - -def set_volume(core, volume): - ''' - Set volume of the mixer. - - :param mopidy.core.Core core: The mopidy core instance - :param volume: The new (percentage) volume - :type volume: int|str - ''' - LOGGER.info('Setting volume to %s', volume) - try: - core.mixer.set_volume(int(volume)) - except ValueError as ex: - LOGGER.error(str(ex)) - - -def play_pause(core): - ''' - Pause or resume the playback. - - :param mopidy.core.Core core: The mopidy core instance - ''' - playback = core.playback - - if playback.get_state().get() == 'playing': - LOGGER.info('Pausing the playback') - playback.pause() - else: - LOGGER.info('Resuming the playback') - playback.resume() - - -def stop(core): - ''' - Stop playback. - - :param mopidy.core.Core core: The mopidy core instance - ''' - LOGGER.info('Stopping playback') - core.playback.stop() - - -def shutdown(core): # pylint: disable=unused-argument - ''' - Shutdown. - - :param mopidy.core.Core core: The mopidy core instance - ''' - LOGGER.info('Shutting down') - system('sudo /sbin/shutdown -h now') diff --git a/mopidy_pummeluff/tags/__init__.py b/mopidy_pummeluff/actions/__init__.py similarity index 54% rename from mopidy_pummeluff/tags/__init__.py rename to mopidy_pummeluff/actions/__init__.py index c1e5a5d..42a9a83 100644 --- a/mopidy_pummeluff/tags/__init__.py +++ b/mopidy_pummeluff/actions/__init__.py @@ -3,19 +3,20 @@ ''' __all__ = ( - 'Tracklist', - 'Volume', 'PlayPause', 'Stop', + 'PreviousTrack', + 'NextTrack', 'Shutdown', + 'Tracklist', + 'Volume', ) +from .playback import PlayPause, Stop, PreviousTrack, NextTrack +from .shutdown import Shutdown from .tracklist import Tracklist from .volume import Volume -from .play_pause import PlayPause -from .stop import Stop -from .shutdown import Shutdown -TAGS = {} -for tag in __all__: - TAGS[tag] = globals()[tag].__doc__.strip() +ACTIONS = {} +for action in __all__: + ACTIONS[action] = globals()[action].__doc__.strip() diff --git a/mopidy_pummeluff/tags/base.py b/mopidy_pummeluff/actions/base.py similarity index 74% rename from mopidy_pummeluff/tags/base.py rename to mopidy_pummeluff/actions/base.py index e160011..a0de670 100644 --- a/mopidy_pummeluff/tags/base.py +++ b/mopidy_pummeluff/actions/base.py @@ -1,22 +1,36 @@ ''' -Python module for Mopidy Pummeluff base tag. +Python module for Mopidy Pummeluff base action. ''' __all__ = ( - 'Tag', + 'Action', ) from logging import getLogger +from inspect import getfullargspec LOGGER = getLogger(__name__) -class Tag: +class Action: ''' Base RFID tag class, which will implement the factory pattern in Python's own :py:meth:`__new__` method. ''' - parameterised = True + + @classmethod + def execute(cls, core): + ''' + Execute the action. + + :param mopidy.core.Core core: The mopidy core instance + + :raises NotImplementedError: When class method is not implemented + ''' + name = cls.__name__ + error = 'Missing execute class method in the %s class' + LOGGER.error(error, name) + raise NotImplementedError(error % name) def __init__(self, uid, alias=None, parameter=None): ''' @@ -56,21 +70,7 @@ def __call__(self, core): args = [core] if self.parameter: args.append(self.parameter) - self.action.__func__(*args) - - @property - def action(self): - ''' - Return an action function defined in the - :py:mod:`mopidy_pummeluff.actions` Python module. - - :return: An action - :raises NotImplementedError: When action property isn't defined - ''' - cls = self.__class__.__name__ - error = 'Missing action property in the %s class' - LOGGER.error(error, cls) - raise NotImplementedError(error % cls) + self.execute(*args) def as_dict(self, include_scanned=False): ''' @@ -99,8 +99,10 @@ def validate(self): :raises ValueError: When parameter is not allowed but defined ''' - if self.parameterised and not self.parameter: + parameterised = len(getfullargspec(self.execute).args) > 2 + + if parameterised and not self.parameter: raise ValueError('Parameter required for this tag') - if not self.parameterised and self.parameter: + if not parameterised and self.parameter: raise ValueError('No parameter allowed for this tag') diff --git a/mopidy_pummeluff/actions/playback.py b/mopidy_pummeluff/actions/playback.py new file mode 100644 index 0000000..8c13a25 --- /dev/null +++ b/mopidy_pummeluff/actions/playback.py @@ -0,0 +1,86 @@ +''' +Python module for Mopidy Pummeluff playback actions. +''' + +__all__ = ( + 'PlayPause', + 'Stop', + 'PreviousTrack', + 'NextTrack', +) + +from logging import getLogger + +from .base import Action + +LOGGER = getLogger(__name__) + + +class PlayPause(Action): + ''' + Pauses or resumes the playback, based on the current state. + ''' + + @classmethod + def execute(cls, core): + ''' + Pause or resume the playback. + + :param mopidy.core.Core core: The mopidy core instance + ''' + playback = core.playback + + if playback.get_state().get() == 'playing': + LOGGER.info('Pausing the playback') + playback.pause() + else: + LOGGER.info('Resuming the playback') + playback.resume() + + +class Stop(Action): + ''' + Stops the playback. + ''' + + @classmethod + def execute(cls, core): + ''' + Stop playback. + + :param mopidy.core.Core core: The mopidy core instance + ''' + LOGGER.info('Stopping playback') + core.playback.stop() + + +class PreviousTrack(Action): + ''' + Changes to the previous track. + ''' + + @classmethod + def execute(cls, core): + ''' + Change to previous track. + + :param mopidy.core.Core core: The mopidy core instance + ''' + LOGGER.info('Changing to previous track') + core.playback.previous() + + +class NextTrack(Action): + ''' + Changes to the next track. + ''' + + @classmethod + def execute(cls, core): + ''' + Change to next track. + + :param mopidy.core.Core core: The mopidy core instance + ''' + LOGGER.info('Changing to next track') + core.playback.next() diff --git a/mopidy_pummeluff/actions/shutdown.py b/mopidy_pummeluff/actions/shutdown.py new file mode 100644 index 0000000..35d5f87 --- /dev/null +++ b/mopidy_pummeluff/actions/shutdown.py @@ -0,0 +1,30 @@ +''' +Python module for Mopidy Pummeluff shutdown tag. +''' + +__all__ = ( + 'Shutdown', +) + +from logging import getLogger +from os import system + +from .base import Action + +LOGGER = getLogger(__name__) + + +class Shutdown(Action): + ''' + Shutting down the system. + ''' + + @classmethod + def execute(cls, core): + ''' + Shutdown. + + :param mopidy.core.Core core: The mopidy core instance + ''' + LOGGER.info('Shutting down') + system('sudo /sbin/shutdown -h now') diff --git a/mopidy_pummeluff/actions/tracklist.py b/mopidy_pummeluff/actions/tracklist.py new file mode 100644 index 0000000..3cbb155 --- /dev/null +++ b/mopidy_pummeluff/actions/tracklist.py @@ -0,0 +1,41 @@ +''' +Python module for Mopidy Pummeluff tracklist tag. +''' + +__all__ = ( + 'Tracklist', +) + +from logging import getLogger + +from .base import Action + +LOGGER = getLogger(__name__) + + +class Tracklist(Action): + ''' + Replaces the current tracklist with the URI retreived from the tag's + parameter. + ''' + + @classmethod + def execute(cls, core, uri): # pylint: disable=arguments-differ + ''' + Replace tracklist and play. + + :param mopidy.core.Core core: The mopidy core instance + :param str uri: An URI for the tracklist replacement + ''' + LOGGER.info('Replacing tracklist with URI "%s"', uri) + + playlists = [playlist.uri for playlist in core.playlists.as_list().get()] + + if uri in playlists: + uris = [item.uri for item in core.playlists.get_items(uri).get()] + else: + uris = [uri] + + core.tracklist.clear() + core.tracklist.add(uris=uris) + core.playback.play() diff --git a/mopidy_pummeluff/tags/volume.py b/mopidy_pummeluff/actions/volume.py similarity index 53% rename from mopidy_pummeluff/tags/volume.py rename to mopidy_pummeluff/actions/volume.py index 504be5d..741e930 100644 --- a/mopidy_pummeluff/tags/volume.py +++ b/mopidy_pummeluff/actions/volume.py @@ -6,16 +6,32 @@ 'Volume', ) -from mopidy_pummeluff.actions import set_volume -from .base import Tag +from logging import getLogger +from .base import Action -class Volume(Tag): +LOGGER = getLogger(__name__) + + +class Volume(Action): ''' Sets the volume to the percentage value retreived from the tag's parameter. ''' - action = set_volume + @classmethod + def execute(cls, core, volume): # pylint: disable=arguments-differ + ''' + Set volume of the mixer. + + :param mopidy.core.Core core: The mopidy core instance + :param volume: The new (percentage) volume + :type volume: int|str + ''' + LOGGER.info('Setting volume to %s', volume) + try: + core.mixer.set_volume(int(volume)) + except ValueError as ex: + LOGGER.error(str(ex)) def validate(self): ''' diff --git a/mopidy_pummeluff/frontend.py b/mopidy_pummeluff/frontend.py index 4f4ae52..a137794 100644 --- a/mopidy_pummeluff/frontend.py +++ b/mopidy_pummeluff/frontend.py @@ -25,7 +25,7 @@ class PummeluffFrontend(pykka.ThreadingActor, mopidy_core.CoreListener): ''' def __init__(self, config, core): # pylint: disable=unused-argument - super(PummeluffFrontend, self).__init__() + super().__init__() self.core = core self.stop_event = Event() self.gpio_handler = GPIOHandler(core=core, stop_event=self.stop_event) diff --git a/mopidy_pummeluff/registry.py b/mopidy_pummeluff/registry.py index 0457cdd..7e23acf 100644 --- a/mopidy_pummeluff/registry.py +++ b/mopidy_pummeluff/registry.py @@ -11,7 +11,7 @@ import json from logging import getLogger -from mopidy_pummeluff import tags +from mopidy_pummeluff import actions LOGGER = getLogger(__name__) @@ -41,32 +41,32 @@ def __init__(self): def unserialize_item(cls, item): ''' Unserialize an item from the persistent storage on filesystem to a - native tag. + native action. :param tuple item: The item - :return: The tag - :rtype: tags.tag + :return: The action + :rtype: actions.Action ''' - return item['uid'], cls.init_tag(**item) + return item['uid'], cls.init_action(**item) @classmethod - def init_tag(cls, tag_class, uid, alias=None, parameter=None): + def init_action(cls, action_class, uid, alias=None, parameter=None): ''' - Initialise a new tag instance. + Initialise a new action instance. - :param str tag_class: The tag class + :param str action_class: The action class :param str uid: The RFID UID :param str alias: The alias :param str parameter: The parameter - :return: The tag instance - :rtype: tags.Tag + :return: The action instance + :rtype: actions.Action ''' - uid = str(uid).strip() - tag_class = getattr(tags, tag_class) + uid = str(uid).strip() + action_class = getattr(actions, action_class) - return tag_class(uid, alias, parameter) + return action_class(uid, alias, parameter) def read(self): ''' @@ -94,35 +94,35 @@ def write(self): os.makedirs(directory) with open(config, 'w') as f: - json.dump([tag.as_dict() for tag in self.values()], f, indent=4) + json.dump([action.as_dict() for action in self.values()], f, indent=4) - def register(self, tag_class, uid, alias=None, parameter=None): + def register(self, action_class, uid, alias=None, parameter=None): ''' Register a new tag in the registry. - :param str tag_class: The tag class + :param str action_class: The action class :param str uid: The UID :param str alias: The alias :param str parameter: The parameter (optional) - :return: The tag - :rtype: tags.Tag + :return: The action + :rtype: actions.Action ''' - LOGGER.info('Registering %s tag %s with parameter "%s"', tag_class, uid, parameter) + LOGGER.info('Registering %s tag %s with parameter "%s"', action_class, uid, parameter) - tag = self.init_tag( - tag_class=tag_class, + action = self.init_action( + action_class=action_class, uid=uid, alias=alias, parameter=parameter ) - tag.validate() + action.validate() - self[uid] = tag + self[uid] = action self.write() - return tag + return action REGISTRY = RegistryDict() diff --git a/mopidy_pummeluff/tags/play_pause.py b/mopidy_pummeluff/tags/play_pause.py deleted file mode 100644 index 11ff3d2..0000000 --- a/mopidy_pummeluff/tags/play_pause.py +++ /dev/null @@ -1,18 +0,0 @@ -''' -Python module for Mopidy Pummeluff play pause tag. -''' - -__all__ = ( - 'PlayPause', -) - -from mopidy_pummeluff.actions import play_pause -from .base import Tag - - -class PlayPause(Tag): - ''' - Pauses or resumes the playback, based on the current state. - ''' - action = play_pause - parameterised = False diff --git a/mopidy_pummeluff/tags/shutdown.py b/mopidy_pummeluff/tags/shutdown.py deleted file mode 100644 index a43b8d6..0000000 --- a/mopidy_pummeluff/tags/shutdown.py +++ /dev/null @@ -1,18 +0,0 @@ -''' -Python module for Mopidy Pummeluff shutdown tag. -''' - -__all__ = ( - 'Shutdown', -) - -from mopidy_pummeluff.actions import shutdown -from .base import Tag - - -class Shutdown(Tag): - ''' - Shutting down the system. - ''' - action = shutdown - parameterised = False diff --git a/mopidy_pummeluff/tags/stop.py b/mopidy_pummeluff/tags/stop.py deleted file mode 100644 index 75e4c81..0000000 --- a/mopidy_pummeluff/tags/stop.py +++ /dev/null @@ -1,18 +0,0 @@ -''' -Python module for Mopidy Pummeluff stop tag. -''' - -__all__ = ( - 'Stop', -) - -from mopidy_pummeluff.actions import stop -from .base import Tag - - -class Stop(Tag): - ''' - Stops the playback. - ''' - action = stop - parameterised = False diff --git a/mopidy_pummeluff/tags/tracklist.py b/mopidy_pummeluff/tags/tracklist.py deleted file mode 100644 index 46c2dab..0000000 --- a/mopidy_pummeluff/tags/tracklist.py +++ /dev/null @@ -1,19 +0,0 @@ -''' -Python module for Mopidy Pummeluff tracklist tag. -''' - -__all__ = ( - 'Tracklist', -) - -from mopidy_pummeluff.actions import replace_tracklist -from .base import Tag - - -class Tracklist(Tag): - ''' - Replaces the current tracklist with the URI retreived from the tag's - parameter. - ''' - - action = replace_tracklist diff --git a/mopidy_pummeluff/threads/gpio_handler.py b/mopidy_pummeluff/threads/gpio_handler.py index 65e62ae..dbeb29e 100644 --- a/mopidy_pummeluff/threads/gpio_handler.py +++ b/mopidy_pummeluff/threads/gpio_handler.py @@ -12,7 +12,7 @@ import RPi.GPIO as GPIO -from mopidy_pummeluff.actions import shutdown, play_pause +from mopidy_pummeluff.actions import Shutdown, PlayPause, Stop, PreviousTrack, NextTrack from mopidy_pummeluff.sound import play_sound LOGGER = getLogger(__name__) @@ -24,8 +24,11 @@ class GPIOHandler(Thread): LED when it's started and then reacting to button presses. ''' button_pins = { - 5: shutdown, - 7: play_pause, + 5: Shutdown, + 29: PlayPause, + 31: Stop, + 33: PreviousTrack, + 35: NextTrack, } led_pin = 8 @@ -76,5 +79,5 @@ def button_push(self, pin): if (GPIO.input(pin) == GPIO.LOW) and (now - before > 0.25): LOGGER.debug('Button at pin %s was pushed', pin) play_sound('success.wav') - self.button_pins[pin](self.core) + self.button_pins[pin].execute(self.core) self.timestamps[pin] = now diff --git a/mopidy_pummeluff/threads/tag_reader.py b/mopidy_pummeluff/threads/tag_reader.py index 97863a4..701b80b 100644 --- a/mopidy_pummeluff/threads/tag_reader.py +++ b/mopidy_pummeluff/threads/tag_reader.py @@ -14,7 +14,7 @@ from pirc522 import RFID from mopidy_pummeluff.registry import REGISTRY -from mopidy_pummeluff.tags.base import Tag +from mopidy_pummeluff.actions.base import Action from mopidy_pummeluff.sound import play_sound LOGGER = getLogger(__name__) @@ -104,15 +104,15 @@ def handle_uid(self, uid): :param str uid: The UID ''' try: - tag = REGISTRY[str(uid)] + action = REGISTRY[str(uid)] LOGGER.info('Triggering action of registered tag') play_sound('success.wav') - tag(self.core) + action(self.core) except KeyError: LOGGER.info('Tag is not registered, thus doing nothing') play_sound('fail.wav') - tag = Tag(uid=uid) + action = Action(uid=uid) - tag.scanned = time() - TagReader.latest = tag + action.scanned = time() + TagReader.latest = action diff --git a/mopidy_pummeluff/web.py b/mopidy_pummeluff/web.py index 46895d9..ef81574 100644 --- a/mopidy_pummeluff/web.py +++ b/mopidy_pummeluff/web.py @@ -6,7 +6,7 @@ 'LatestHandler', 'RegistryHandler', 'RegisterHandler', - 'TagClassesHandler', + 'ActionClassesHandler', ) from json import dumps @@ -15,7 +15,7 @@ from tornado.web import RequestHandler from mopidy_pummeluff.registry import REGISTRY -from mopidy_pummeluff.tags import TAGS +from mopidy_pummeluff.actions import ACTIONS from mopidy_pummeluff.threads import TagReader LOGGER = getLogger(__name__) @@ -111,7 +111,7 @@ def post(self, *args, **kwargs): # pylint: disable=unused-argument ''' try: tag = REGISTRY.register( - tag_class=self.get_argument('tag-class'), + action_class=self.get_argument('action-class'), uid=self.get_argument('uid'), alias=self.get_argument('alias', None), parameter=self.get_argument('parameter', None), @@ -141,9 +141,9 @@ def put(self, *args, **kwargs): # pylint: disable=unused-argument self.post() -class TagClassesHandler(RequestHandler): # pylint: disable=abstract-method +class ActionClassesHandler(RequestHandler): # pylint: disable=abstract-method ''' - Request handler which returns all tag classes. + Request handler which returns all action classes. ''' def initialize(self, core): # pylint: disable=arguments-differ @@ -160,8 +160,8 @@ def get(self, *args, **kwargs): # pylint: disable=unused-argument ''' data = { 'success': True, - 'message': 'Tag classes successfully retreived', - 'tag_classes': TAGS + 'message': 'Action classes successfully retreived', + 'action_classes': ACTIONS } self.set_header('Content-type', 'application/json') diff --git a/mopidy_pummeluff/webui/index.html b/mopidy_pummeluff/webui/index.html index 4298bd7..1f45238 100644 --- a/mopidy_pummeluff/webui/index.html +++ b/mopidy_pummeluff/webui/index.html @@ -21,8 +21,8 @@

Register New Tag

Read UID from RFID tag… - - diff --git a/mopidy_pummeluff/webui/script.js b/mopidy_pummeluff/webui/script.js index 73b0ed0..ea05d42 100644 --- a/mopidy_pummeluff/webui/script.js +++ b/mopidy_pummeluff/webui/script.js @@ -40,7 +40,7 @@ class API { let tagElement = document.createElement('div') tagElement.setAttribute('class', 'tag') - let args = new Array('alias', 'uid', 'tag_class', 'parameter') + let args = new Array('alias', 'uid', 'action_class', 'parameter') for(let arg of args) { let spanElement = document.createElement('span') @@ -61,24 +61,24 @@ class API { * Refresh the tags. */ - refreshTagClasses() + refreshActionClasses() { let callback = function(response) { - let select = document.getElementById('tag-class'); + let select = document.getElementById('action-class'); while(select.firstChild) select.removeChild(select.firstChild) - for(let tag_class in response.tag_classes) + for(let action_class in response.action_classes) { let option = document.createElement('option') - option.setAttribute('value', tag_class) - option.innerHTML = tag_class + ' (' + response.tag_classes[tag_class] + ')' + option.setAttribute('value', action_class) + option.innerHTML = action_class + ' (' + response.action_classes[action_class] + ')' select.appendChild(option) } } - this.request('/pummeluff/tag-classes/', false, callback) + this.request('/pummeluff/action-classes/', false, callback) } /* @@ -98,7 +98,7 @@ class API { document.getElementById('uid').value = '' document.getElementById('alias').value = '' document.getElementById('parameter').value = '' - document.getElementById('tag-class').selectIndex = 0 + document.getElementById('action-class').selectIndex = 0 } else { @@ -120,12 +120,12 @@ class API { let uid_field = document.getElementById('uid') let alias_field = document.getElementById('alias') let parameter_field = document.getElementById('parameter') - let tag_class_select = document.getElementById('tag-class') + let action_class_select = document.getElementById('action-class') uid_field.value = '' alias_field.value = '' parameter_field.value = '' - tag_class_select.selectIndex = 0 + action_class_select.selectIndex = 0 let link = document.getElementById('read-rfid-tag') link.classList.add('reading') @@ -144,8 +144,8 @@ class API { if(response.parameter) parameter_field.value = response.parameter - if(response.tag_class) - tag_class_select.value = response.tag_class + if(response.action_class) + action_class_select.value = response.action_class link.classList.remove('reading') } @@ -168,7 +168,7 @@ class API { api = new API() api.refreshRegistry(); -api.refreshTagClasses(); +api.refreshActionClasses(); document.addEventListener('click', function(event) { diff --git a/mopidy_pummeluff/webui/style.css b/mopidy_pummeluff/webui/style.css index f3dfa24..dc39d7e 100644 --- a/mopidy_pummeluff/webui/style.css +++ b/mopidy_pummeluff/webui/style.css @@ -145,13 +145,13 @@ div.tag } div.tag span.uid, -div.tag span.tag-class, +div.tag span.action-class, div.tag span.parameter { font-family: Courier New, monospace; } -div.tag span.tag-class +div.tag span.action-class { display : inline-block; background-color: #888; From fbe9560458f1660c1ed52df6013b1ba6ff6a849a Mon Sep 17 00:00:00 2001 From: Dominique Barton Date: Wed, 15 Apr 2020 12:59:46 +0200 Subject: [PATCH 2/3] FIX: Make old-style tags.json compatible again --- mopidy_pummeluff/actions/base.py | 2 +- mopidy_pummeluff/registry.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mopidy_pummeluff/actions/base.py b/mopidy_pummeluff/actions/base.py index a0de670..1e1ac69 100644 --- a/mopidy_pummeluff/actions/base.py +++ b/mopidy_pummeluff/actions/base.py @@ -82,7 +82,7 @@ def as_dict(self, include_scanned=False): :rtype: dict ''' data = { - 'tag_class': self.__class__.__name__, + 'action_class': self.__class__.__name__, 'uid': self.uid, 'alias': self.alias or '', 'parameter': self.parameter or '', diff --git a/mopidy_pummeluff/registry.py b/mopidy_pummeluff/registry.py index 7e23acf..8136666 100644 --- a/mopidy_pummeluff/registry.py +++ b/mopidy_pummeluff/registry.py @@ -48,6 +48,9 @@ def unserialize_item(cls, item): :return: The action :rtype: actions.Action ''' + if 'tag_class' in item: + item['action_class'] = item.pop('tag_class') + return item['uid'], cls.init_action(**item) @classmethod From 5858745c1d9b8cfaba6f53b5bc5861394405670c Mon Sep 17 00:00:00 2001 From: Dominique Barton Date: Wed, 15 Apr 2020 13:31:04 +0200 Subject: [PATCH 3/3] FEATURE: Add unregister action --- mopidy_pummeluff/__init__.py | 12 +-- mopidy_pummeluff/registry.py | 11 +++ mopidy_pummeluff/web.py | 63 ++++++++-------- mopidy_pummeluff/webui/index.html | 1 + mopidy_pummeluff/webui/script.js | 121 ++++++++++++++---------------- mopidy_pummeluff/webui/style.css | 56 ++++++++------ 6 files changed, 143 insertions(+), 121 deletions(-) diff --git a/mopidy_pummeluff/__init__.py b/mopidy_pummeluff/__init__.py index 09ce374..5dd1932 100644 --- a/mopidy_pummeluff/__init__.py +++ b/mopidy_pummeluff/__init__.py @@ -7,7 +7,8 @@ import mopidy from .frontend import PummeluffFrontend -from .web import LatestHandler, RegistryHandler, RegisterHandler, ActionClassesHandler +from .web import LatestHandler, RegistryHandler, RegisterHandler, UnregisterHandler, \ + ActionClassesHandler def app_factory(config, core): # pylint: disable=unused-argument @@ -21,10 +22,11 @@ def app_factory(config, core): # pylint: disable=unused-argument :rtype: list ''' return [ - ('/latest/', LatestHandler, {'core': core}), - ('/registry/', RegistryHandler, {'core': core}), - ('/register/', RegisterHandler, {'core': core}), - ('/action-classes/', ActionClassesHandler, {'core': core}), + ('/latest/', LatestHandler), + ('/registry/', RegistryHandler), + ('/register/', RegisterHandler), + ('/unregister/', UnregisterHandler), + ('/action-classes/', ActionClassesHandler), ] diff --git a/mopidy_pummeluff/registry.py b/mopidy_pummeluff/registry.py index 8136666..9070e98 100644 --- a/mopidy_pummeluff/registry.py +++ b/mopidy_pummeluff/registry.py @@ -127,5 +127,16 @@ def register(self, action_class, uid, alias=None, parameter=None): return action + def unregister(self, uid): + ''' + Unregister a tag from the registry. + + :param str uid: The UID + ''' + LOGGER.info('Unregistering tag %s', uid) + + del self[uid] + self.write() + REGISTRY = RegistryDict() diff --git a/mopidy_pummeluff/web.py b/mopidy_pummeluff/web.py index ef81574..e3c47d9 100644 --- a/mopidy_pummeluff/web.py +++ b/mopidy_pummeluff/web.py @@ -6,6 +6,7 @@ 'LatestHandler', 'RegistryHandler', 'RegisterHandler', + 'UnregisterHandler', 'ActionClassesHandler', ) @@ -26,14 +27,6 @@ class LatestHandler(RequestHandler): # pylint: disable=abstract-method Request handler which returns the latest scanned tag. ''' - def initialize(self, core): # pylint: disable=arguments-differ - ''' - Initialize request handler with Mopidy core. - - :param mopidy.core.Core mopidy_core: The mopidy core instance - ''' - self.core = core # pylint: disable=attribute-defined-outside-init - def get(self, *args, **kwargs): # pylint: disable=unused-argument ''' Handle GET request. @@ -65,14 +58,6 @@ class RegistryHandler(RequestHandler): # pylint: disable=abstract-method Request handler which returns all registered tags. ''' - def initialize(self, core): # pylint: disable=arguments-differ - ''' - Initialize request handler with Mopidy core. - - :param mopidy.core.Core mopidy_core: The mopidy core instance - ''' - self.core = core # pylint: disable=attribute-defined-outside-init - def get(self, *args, **kwargs): # pylint: disable=unused-argument ''' Handle GET request. @@ -97,14 +82,6 @@ class RegisterHandler(RequestHandler): # pylint: disable=abstract-method Request handler which registers an RFID tag in the registry. ''' - def initialize(self, core): # pylint: disable=arguments-differ - ''' - Initialize request handler with Mopidy core. - - :param mopidy.core.Core mopidy_core: The mopidy core instance - ''' - self.core = core # pylint: disable=attribute-defined-outside-init - def post(self, *args, **kwargs): # pylint: disable=unused-argument ''' Handle POST request. @@ -141,18 +118,44 @@ def put(self, *args, **kwargs): # pylint: disable=unused-argument self.post() -class ActionClassesHandler(RequestHandler): # pylint: disable=abstract-method +class UnregisterHandler(RequestHandler): # pylint: disable=abstract-method ''' - Request handler which returns all action classes. + Request handler which unregisters an RFID tag from the registry. ''' - def initialize(self, core): # pylint: disable=arguments-differ + def post(self, *args, **kwargs): # pylint: disable=unused-argument + ''' + Handle POST request. ''' - Initialize request handler with Mopidy core. + try: + REGISTRY.unregister(uid=self.get_argument('uid')) + + data = { + 'success': True, + 'message': 'Tag successfully unregistered', + } + + except ValueError as ex: + self.set_status(400) + data = { + 'success': False, + 'message': str(ex) + } + + self.set_header('Content-type', 'application/json') + self.write(dumps(data)) - :param mopidy.core.Core mopidy_core: The mopidy core instance + def put(self, *args, **kwargs): # pylint: disable=unused-argument + ''' + Handle PUT request. ''' - self.core = core # pylint: disable=attribute-defined-outside-init + self.post() + + +class ActionClassesHandler(RequestHandler): # pylint: disable=abstract-method + ''' + Request handler which returns all action classes. + ''' def get(self, *args, **kwargs): # pylint: disable=unused-argument ''' diff --git a/mopidy_pummeluff/webui/index.html b/mopidy_pummeluff/webui/index.html index 1f45238..a8f75af 100644 --- a/mopidy_pummeluff/webui/index.html +++ b/mopidy_pummeluff/webui/index.html @@ -27,6 +27,7 @@

Register New Tag

+ diff --git a/mopidy_pummeluff/webui/script.js b/mopidy_pummeluff/webui/script.js index ea05d42..14527da 100644 --- a/mopidy_pummeluff/webui/script.js +++ b/mopidy_pummeluff/webui/script.js @@ -8,15 +8,13 @@ class API { * Send AJAX request to REST API endpoint. */ - request(endpoint, data, callback) - { + request = (endpoint, data, callback) => { let init = {} if(data) init = { method: 'POST', body: data } fetch(endpoint, init) - .then(function(response) - { + .then((response) => { return response.json() }) .then(callback) @@ -27,24 +25,21 @@ class API { * Refresh the registry. */ - refreshRegistry() - { - let callback = function(response) - { + refreshRegistry = () => { + let callback = (response) => { let tagsContainer = document.getElementById('tags') - while(tagsContainer.firstChild) + while(tagsContainer.firstChild) { tagsContainer.removeChild(tagsContainer.firstChild) + } - for(let tag of response.tags) - { + for(let tag of response.tags) { let tagElement = document.createElement('div') tagElement.setAttribute('class', 'tag') let args = new Array('alias', 'uid', 'action_class', 'parameter') - for(let arg of args) - { + for(let arg of args) { let spanElement = document.createElement('span') - let value = tag[arg] ? tag[arg] : '-' + let value = tag[arg] ? tag[arg] : '-' spanElement.setAttribute('class', arg.replace('_', '-')) spanElement.innerHTML = value tagElement.appendChild(spanElement) @@ -61,16 +56,13 @@ class API { * Refresh the tags. */ - refreshActionClasses() - { - let callback = function(response) - { + refreshActionClasses = () => { + let callback = (response) => { let select = document.getElementById('action-class'); while(select.firstChild) select.removeChild(select.firstChild) - for(let action_class in response.action_classes) - { + for(let action_class in response.action_classes) { let option = document.createElement('option') option.setAttribute('value', action_class) option.innerHTML = action_class + ' (' + response.action_classes[action_class] + ')' @@ -81,45 +73,52 @@ class API { this.request('/pummeluff/action-classes/', false, callback) } + /* + * Reset the form. + */ + + formCallback = (response) => { + if(response.success) { + this.refreshRegistry() + document.getElementById('uid').value = '' + document.getElementById('alias').value = '' + document.getElementById('parameter').value = '' + document.getElementById('action-class').selectIndex = 0 + } else { + window.alert(response.message) + } + } + /* * Register a new tag. */ - register() - { + register = () => { let form = document.getElementById('register-form') let data = new FormData(form) + this.request('/pummeluff/register/', data, this.formCallback) + } - let callback = function(response) - { - if(response.success) - { - api.refreshRegistry() - document.getElementById('uid').value = '' - document.getElementById('alias').value = '' - document.getElementById('parameter').value = '' - document.getElementById('action-class').selectIndex = 0 - } - else - { - window.alert(response.message) - } - } + /* + * Unregister an existing tag. + */ - this.request('/pummeluff/register/', data, callback) + unregister = () => { + let form = document.getElementById('register-form') + let data = new FormData(form) + this.request('/pummeluff/unregister/', data, this.formCallback) } /* * Get latest scanned tag. */ - getLatestTag() - { + getLatestTag = () => { let latest_tag = undefined - let uid_field = document.getElementById('uid') - let alias_field = document.getElementById('alias') - let parameter_field = document.getElementById('parameter') + let uid_field = document.getElementById('uid') + let alias_field = document.getElementById('alias') + let parameter_field = document.getElementById('parameter') let action_class_select = document.getElementById('action-class') uid_field.value = '' @@ -127,15 +126,12 @@ class API { parameter_field.value = '' action_class_select.selectIndex = 0 - let link = document.getElementById('read-rfid-tag') + let link = document.getElementById('read-rfid-tag') link.classList.add('reading') - let do_request = function() - { - let callback = function(response) - { - if(latest_tag && response.success && JSON.stringify(response) != JSON.stringify(latest_tag)) - { + let do_request = () => { + let callback = (response) => { + if(latest_tag && response.success && JSON.stringify(response) != JSON.stringify(latest_tag)) { uid_field.value = response.uid if(response.alias) @@ -148,9 +144,7 @@ class API { action_class_select.value = response.action_class link.classList.remove('reading') - } - else - { + } else { setTimeout(() => do_request(), 1000) } @@ -167,27 +161,28 @@ class API { api = new API() -api.refreshRegistry(); -api.refreshActionClasses(); +api.refreshRegistry() +api.refreshActionClasses() -document.addEventListener('click', function(event) -{ +document.addEventListener('click', (event) => { let target = event.target let div = target.closest('div') - if(div && div.classList.contains('tag')) - { - for(let child of div.children) - { + if(div && div.classList.contains('tag')) { + for(let child of div.children) { document.getElementById(child.className).value = child.innerHTML.replace(/^-$/, '') } } }) -document.getElementById('register-form').onsubmit = function() -{ +document.getElementById('register-form').onsubmit = () => { api.register() return false; } +document.getElementById('unregister-button').onclick = () => { + api.unregister() + return false +} + document.getElementById('read-rfid-tag').onclick = () => api.getLatestTag() diff --git a/mopidy_pummeluff/webui/style.css b/mopidy_pummeluff/webui/style.css index dc39d7e..15488dc 100644 --- a/mopidy_pummeluff/webui/style.css +++ b/mopidy_pummeluff/webui/style.css @@ -18,11 +18,11 @@ body body { - min-height : 100vh; + background-color: #222; + color : #eee; font-family : sans-serif; font-size : 14px; - color : #eee; - background-color: #222; + min-height : 100vh; } /* @@ -80,15 +80,15 @@ input, select, button { - width : 100%; + background-color: #222; border : 0; - padding : 10px; - border-width : 0 0 2px 0; - border-style : solid; border-color : #333; - background-color: #222; + border-style : solid; + border-width : 0 0 2px 0; color : #eee; outline : none; + padding : 10px; + width : 100%; } select @@ -103,26 +103,36 @@ input::placeholder input:focus { - border-bottom-color: #fa0; + border-bottom-color: #8ff; } button +{ + color : #eee; + cursor : pointer; + font-weight: bold; + margin-top : 10px; +} + +button#register-button { background-color: #4a4; - color : #eee; - font-weight : bold; - margin-top : 10px; +} + +button#unregister-button +{ + background-color: #a20; } #read-rfid-tag { - text-decoration: none; - color : #fa0; + color : #8ff; font-size : 11px; + text-decoration: none; } #read-rfid-tag.reading { - animation: blink 0.5s cubic-bezier(.5, 0, 1, 1) infinite alternate; + animation: blink 0.5s cubic-bezier(.5, 0, 1, 1) infinite alternate; } @keyframes blink { to { opacity: 0.25; } } @@ -133,15 +143,15 @@ button div.tag { - display : inline-block; - cursor : pointer; background-color: #eee; - color : #222; box-shadow : 1px 1px 5px #000; - padding : 10px; + color : #222; + cursor : pointer; + display : inline-block; + line-height : 20px; margin : 0 20px 20px 0; + padding : 10px; width : 400px; - line-height : 20px; } div.tag span.uid, @@ -153,14 +163,14 @@ div.tag span.parameter div.tag span.action-class { - display : inline-block; background-color: #888; + border-radius : 10px; color : #eee; + display : inline-block; font-size : 11px; line-height : 11px; - padding : 3px 5px; margin-left : 5px; - border-radius : 10px; + padding : 3px 5px; } div.tag span.alias,