From 9064058a030efd002ac78954c09e2f3122a038d7 Mon Sep 17 00:00:00 2001 From: Josh Bendavid Date: Thu, 2 Jan 2020 16:30:20 -0500 Subject: [PATCH] Add generic command functionality to denonavr (#29295) * Add generic command functionality to denonavr * add minimal unit tests for denonavr * fix import order * simplify denonavr unit test * handle domain specific service calls with dispatcher * update unit tests * update unit tests * remove unnecessary return value * fix handling of mock instances in unit tests --- homeassistant/components/denonavr/__init__.py | 34 +++++++++++ .../components/denonavr/media_player.py | 24 ++++++++ .../components/denonavr/services.yaml | 11 ++++ requirements_test_all.txt | 3 + tests/components/denonavr/__init__.py | 1 + .../components/denonavr/test_media_player.py | 57 +++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 homeassistant/components/denonavr/services.yaml create mode 100644 tests/components/denonavr/__init__.py create mode 100644 tests/components/denonavr/test_media_player.py diff --git a/homeassistant/components/denonavr/__init__.py b/homeassistant/components/denonavr/__init__.py index dee84449d13..8877a7dfb3b 100644 --- a/homeassistant/components/denonavr/__init__.py +++ b/homeassistant/components/denonavr/__init__.py @@ -1 +1,35 @@ """The denonavr component.""" +import voluptuous as vol + +from homeassistant.const import ATTR_ENTITY_ID +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import dispatcher_send + +DOMAIN = "denonavr" + +SERVICE_GET_COMMAND = "get_command" +ATTR_COMMAND = "command" + +CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids}) + +GET_COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string}) + +SERVICE_TO_METHOD = { + SERVICE_GET_COMMAND: {"method": "get_command", "schema": GET_COMMAND_SCHEMA} +} + + +def setup(hass, config): + """Set up the denonavr platform.""" + + def service_handler(service): + method = SERVICE_TO_METHOD.get(service.service) + data = service.data.copy() + data["method"] = method["method"] + dispatcher_send(hass, DOMAIN, data) + + for service in SERVICE_TO_METHOD: + schema = SERVICE_TO_METHOD[service]["schema"] + hass.services.register(DOMAIN, service, service_handler, schema=schema) + + return True diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 1725b2d105c..46d22187ce1 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -24,16 +24,21 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TIMEOUT, CONF_ZONE, + ENTITY_MATCH_ALL, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -190,6 +195,21 @@ class DenonDevice(MediaPlayerDevice): self._sound_mode_support and SUPPORT_SELECT_SOUND_MODE ) + async def async_added_to_hass(self): + """Register signal handler.""" + async_dispatcher_connect(self.hass, DOMAIN, self.signal_handler) + + def signal_handler(self, data): + """Handle domain-specific signal by calling appropriate method.""" + entity_ids = data[ATTR_ENTITY_ID] + if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids: + params = { + key: value + for key, value in data.items() + if key not in ["entity_id", "method"] + } + getattr(self, data["method"])(**params) + def update(self): """Get the latest status information from device.""" self._receiver.update() @@ -398,3 +418,7 @@ class DenonDevice(MediaPlayerDevice): def mute_volume(self, mute): """Send mute command.""" return self._receiver.mute(mute) + + def get_command(self, command, **kwargs): + """Send generic command.""" + self._receiver.send_get_command(command) diff --git a/homeassistant/components/denonavr/services.yaml b/homeassistant/components/denonavr/services.yaml new file mode 100644 index 00000000000..889adc3af05 --- /dev/null +++ b/homeassistant/components/denonavr/services.yaml @@ -0,0 +1,11 @@ +# Describes the format for available webostv services + +get_command: + description: 'Send a generic http get command.' + fields: + entity_id: + description: Name(s) of the denonavr entities where to run the API method. + example: 'media_player.living_room_receiver' + command: + description: Endpoint of the command, including associated parameters. + example: '/goform/formiPhoneAppDirect.xml?RCKSK0410370' diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ff1ded4434c..373abe92ec8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -149,6 +149,9 @@ datadog==0.15.0 # homeassistant.components.ssdp defusedxml==0.6.0 +# homeassistant.components.denonavr +denonavr==0.7.10 + # homeassistant.components.directv directpy==0.5 diff --git a/tests/components/denonavr/__init__.py b/tests/components/denonavr/__init__.py new file mode 100644 index 00000000000..5ad16068f2a --- /dev/null +++ b/tests/components/denonavr/__init__.py @@ -0,0 +1 @@ +"""Tests for the denonavr integration.""" diff --git a/tests/components/denonavr/test_media_player.py b/tests/components/denonavr/test_media_player.py new file mode 100644 index 00000000000..91bc2abf94d --- /dev/null +++ b/tests/components/denonavr/test_media_player.py @@ -0,0 +1,57 @@ +"""The tests for the denonavr media player platform.""" +from unittest.mock import patch + +import pytest + +from homeassistant.components import media_player +from homeassistant.components.denonavr import ATTR_COMMAND, DOMAIN, SERVICE_GET_COMMAND +from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PLATFORM +from homeassistant.setup import async_setup_component + +NAME = "fake" +ENTITY_ID = f"{media_player.DOMAIN}.{NAME}" + + +@pytest.fixture(name="client") +def client_fixture(): + """Patch of client library for tests.""" + with patch( + "homeassistant.components.denonavr.media_player.denonavr.DenonAVR", + autospec=True, + ) as mock_client_class, patch( + "homeassistant.components.denonavr.media_player.denonavr.discover" + ): + mock_client_class.return_value.name = NAME + mock_client_class.return_value.zones = {"Main": mock_client_class.return_value} + yield mock_client_class.return_value + + +async def setup_denonavr(hass): + """Initialize webostv and media_player for tests.""" + assert await async_setup_component( + hass, + media_player.DOMAIN, + { + media_player.DOMAIN: { + CONF_PLATFORM: "denonavr", + CONF_HOST: "fake", + CONF_NAME: NAME, + } + }, + ) + await hass.async_block_till_done() + + +async def test_get_command(hass, client): + """Test generic command functionality.""" + + await setup_denonavr(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_COMMAND: "test", + } + await hass.services.async_call(DOMAIN, SERVICE_GET_COMMAND, data) + await hass.async_block_till_done() + + client.send_get_command.assert_called_with("test")