diff --git a/homeassistant/components/samsungtv/__init__.py b/homeassistant/components/samsungtv/__init__.py index 1aaa657eea5..1e5cfef90a8 100644 --- a/homeassistant/components/samsungtv/__init__.py +++ b/homeassistant/components/samsungtv/__init__.py @@ -1,7 +1,15 @@ """The Samsung TV integration.""" +from wakeonlan import BROADCAST_IP import voluptuous as vol -from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_TIMEOUT +from homeassistant.const import ( + CONF_BROADCAST_ADDRESS, + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PORT, + CONF_TIMEOUT, +) import homeassistant.helpers.config_validation as cv from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN @@ -20,6 +28,9 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional( CONF_TIMEOUT, default=DEFAULT_TIMEOUT ): cv.positive_int, + vol.Optional( + CONF_BROADCAST_ADDRESS, default=BROADCAST_IP + ): cv.string, } ) ] diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index fc7bbe7c34f..b7db8044a9d 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( + CONF_BROADCAST_ADDRESS, CONF_HOST, CONF_ID, CONF_IP_ADDRESS, @@ -87,6 +88,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" + self._broadcast = None self._host = None self._ip = None self._mac = None @@ -102,6 +104,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry( title=self._title, data={ + CONF_BROADCAST_ADDRESS: self._broadcast, CONF_HOST: self._host, CONF_ID: self._uuid, CONF_IP_ADDRESS: self._ip, @@ -124,6 +127,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if _is_already_configured(self.hass, ip_address): return self.async_abort(reason="already_configured") + self._broadcast = user_input.get(CONF_BROADCAST_ADDRESS) self._host = user_input.get(CONF_HOST) self._ip = self.context[CONF_IP_ADDRESS] = ip_address self._mac = user_input.get(CONF_MAC) diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index e7ddf847f37..4b8c274200f 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -1,17 +1,12 @@ """Support for interface with an Samsung TV.""" import asyncio from datetime import timedelta -import socket from samsungctl import exceptions as samsung_exceptions, Remote as SamsungRemote import voluptuous as vol import wakeonlan -from homeassistant.components.media_player import ( - MediaPlayerDevice, - PLATFORM_SCHEMA, - DEVICE_CLASS_TV, -) +from homeassistant.components.media_player import MediaPlayerDevice, DEVICE_CLASS_TV from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -30,7 +25,6 @@ from homeassistant.const import ( CONF_HOST, CONF_ID, CONF_MAC, - CONF_NAME, CONF_PORT, CONF_TIMEOUT, STATE_OFF, @@ -39,19 +33,10 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.util import dt as dt_util -from .const import ( - CONF_MANUFACTURER, - CONF_MODEL, - DEFAULT_NAME, - DEFAULT_TIMEOUT, - DOMAIN, - LOGGER, - METHODS, -) +from .const import CONF_MANUFACTURER, CONF_MODEL, DOMAIN, LOGGER, METHODS DEFAULT_BROADCAST_ADDRESS = "255.255.255.255" KEY_PRESS_TIMEOUT = 1.2 -KNOWN_DEVICES_KEY = "samsungtv_known_devices" SOURCES = {"TV": "KEY_TV", "HDMI": "KEY_HDMI"} SUPPORT_SAMSUNGTV = ( @@ -66,67 +51,17 @@ SUPPORT_SAMSUNGTV = ( | SUPPORT_PLAY_MEDIA ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_MAC): cv.string, - vol.Optional( - CONF_BROADCAST_ADDRESS, default=DEFAULT_BROADCAST_ADDRESS - ): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - } -) - -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Samsung TV platform.""" - known_devices = hass.data.get(KNOWN_DEVICES_KEY) - if known_devices is None: - known_devices = set() - hass.data[KNOWN_DEVICES_KEY] = known_devices - - uuid = None - # Is this a manual configuration? - if config.get(CONF_HOST) is not None: - host = config.get(CONF_HOST) - port = config.get(CONF_PORT) - name = config.get(CONF_NAME) - mac = config.get(CONF_MAC) - broadcast = config.get(CONF_BROADCAST_ADDRESS) - timeout = config.get(CONF_TIMEOUT) - elif discovery_info is not None: - tv_name = discovery_info.get("name") - model = discovery_info.get("model_name") - host = discovery_info.get("host") - name = f"{tv_name} ({model})" - if name.startswith("[TV]"): - name = name[4:] - port = None - timeout = DEFAULT_TIMEOUT - mac = None - broadcast = DEFAULT_BROADCAST_ADDRESS - uuid = discovery_info.get("udn") - if uuid and uuid.startswith("uuid:"): - uuid = uuid[len("uuid:") :] - - # Only add a device once, so discovered devices do not override manual - # config. - ip_addr = socket.gethostbyname(host) - if ip_addr not in known_devices: - known_devices.add(ip_addr) - add_entities([SamsungTVDevice(host, port, name, timeout, mac, broadcast, uuid)]) - LOGGER.info("Samsung TV %s added as '%s'", host, name) - else: - LOGGER.info("Ignoring duplicate Samsung TV %s", host) + pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Samsung TV from a config entry.""" - host = config_entry.data.get(CONF_HOST) + host = config_entry.data[CONF_HOST] mac = config_entry.data.get(CONF_MAC) - broadcast = config_entry.data.get(CONF_BROADCAST_ADDRESS, DEFAULT_BROADCAST_ADDRESS) + broadcast = config_entry.data.get(CONF_BROADCAST_ADDRESS) or wakeonlan.BROADCAST_IP manufacturer = config_entry.data.get(CONF_MANUFACTURER) model = config_entry.data.get(CONF_MODEL) name = config_entry.title diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index 470de4bd966..e997a373e41 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -47,11 +47,7 @@ def remote_fixture(): """Patch the samsungctl Remote.""" with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( "homeassistant.components.samsungtv.config_flow.Remote" - ), patch( - "homeassistant.components.samsungtv.media_player.SamsungRemote" - ) as remote, patch( - "homeassistant.components.samsungtv.media_player.socket" - ): + ), patch("homeassistant.components.samsungtv.media_player.SamsungRemote") as remote: yield remote diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 8f6014463f5..f8cef28cdb3 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -8,6 +8,7 @@ from asynctest import mock import pytest from samsungctl import exceptions from tests.common import async_fire_time_changed +import wakeonlan from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.components.media_player.const import ( @@ -22,6 +23,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_URL, ) +from homeassistant.components import samsungtv from homeassistant.components.samsungtv.const import DOMAIN as SAMSUNGTV_DOMAIN from homeassistant.components.samsungtv.media_player import ( CONF_TIMEOUT, @@ -36,7 +38,6 @@ from homeassistant.const import ( CONF_HOST, CONF_MAC, CONF_NAME, - CONF_PLATFORM, CONF_PORT, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, @@ -51,70 +52,75 @@ from homeassistant.const import ( STATE_ON, STATE_UNKNOWN, ) -from homeassistant.helpers.discovery import async_load_platform -from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util ENTITY_ID = f"{DOMAIN}.fake" MOCK_CONFIG = { - DOMAIN: { - CONF_PLATFORM: SAMSUNGTV_DOMAIN, - CONF_HOST: "fake", - CONF_NAME: "fake", - CONF_PORT: 8001, - CONF_TIMEOUT: 10, - CONF_MAC: "38:f9:d3:82:b4:f1", - } + SAMSUNGTV_DOMAIN: [ + { + CONF_HOST: "fake", + CONF_NAME: "fake", + CONF_PORT: 8001, + CONF_TIMEOUT: 10, + CONF_MAC: "38:f9:d3:82:b4:f1", + } + ] } ENTITY_ID_BROADCAST = f"{DOMAIN}.fake_broadcast" MOCK_CONFIG_BROADCAST = { - DOMAIN: { - CONF_PLATFORM: SAMSUNGTV_DOMAIN, - CONF_HOST: "fake_broadcast", - CONF_NAME: "fake_broadcast", - CONF_PORT: 8001, - CONF_TIMEOUT: 10, - CONF_MAC: "38:f9:d3:82:b4:f1", - CONF_BROADCAST_ADDRESS: "192.168.5.255", - } + SAMSUNGTV_DOMAIN: [ + { + CONF_HOST: "fake_broadcast", + CONF_NAME: "fake_broadcast", + CONF_PORT: 8001, + CONF_TIMEOUT: 10, + CONF_MAC: "38:f9:d3:82:b4:f1", + CONF_BROADCAST_ADDRESS: "192.168.5.255", + } + ] } ENTITY_ID_NOMAC = f"{DOMAIN}.fake_nomac" MOCK_CONFIG_NOMAC = { - DOMAIN: { - CONF_PLATFORM: SAMSUNGTV_DOMAIN, - CONF_HOST: "fake_nomac", - CONF_NAME: "fake_nomac", - CONF_PORT: 55000, - CONF_TIMEOUT: 10, - } + SAMSUNGTV_DOMAIN: [ + { + CONF_HOST: "fake_nomac", + CONF_NAME: "fake_nomac", + CONF_PORT: 55000, + CONF_TIMEOUT: 10, + } + ] } ENTITY_ID_AUTO = f"{DOMAIN}.fake_auto" MOCK_CONFIG_AUTO = { - DOMAIN: { - CONF_PLATFORM: SAMSUNGTV_DOMAIN, - CONF_HOST: "fake_auto", - CONF_NAME: "fake_auto", - } + SAMSUNGTV_DOMAIN: [{CONF_HOST: "fake_auto", CONF_NAME: "fake_auto"}] } ENTITY_ID_DISCOVERY = f"{DOMAIN}.fake_discovery_fake_model" MOCK_CONFIG_DISCOVERY = { - "name": "fake_discovery", - "model_name": "fake_model", - "host": "fake_host", - "udn": "fake_uuid", + SAMSUNGTV_DOMAIN: [ + { + "name": "fake_discovery", + "model_name": "fake_model", + "host": "fake_host", + "udn": "fake_uuid", + } + ] } ENTITY_ID_DISCOVERY_PREFIX = f"{DOMAIN}.fake_discovery_prefix_fake_model_prefix" MOCK_CONFIG_DISCOVERY_PREFIX = { - "name": "[TV]fake_discovery_prefix", - "model_name": "fake_model_prefix", - "host": "fake_host_prefix", - "udn": "uuid:fake_uuid_prefix", + DOMAIN: [ + { + "name": "[TV]fake_discovery_prefix", + "model_name": "fake_model_prefix", + "host": "fake_host_prefix", + "udn": "uuid:fake_uuid_prefix", + } + ] } AUTODETECT_WEBSOCKET = { @@ -124,7 +130,7 @@ AUTODETECT_WEBSOCKET = { "method": "websocket", "port": None, "host": "fake_auto", - "timeout": 1, + "timeout": None, } AUTODETECT_LEGACY = { "name": "HomeAssistant", @@ -133,22 +139,20 @@ AUTODETECT_LEGACY = { "method": "legacy", "port": None, "host": "fake_auto", - "timeout": 1, + "timeout": None, } @pytest.fixture(name="remote") def remote_fixture(): """Patch the samsungctl Remote.""" - with patch( + with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( + "homeassistant.components.samsungtv.config_flow.Remote" + ), patch( "homeassistant.components.samsungtv.media_player.SamsungRemote" - ) as remote_class, patch( - "homeassistant.components.samsungtv.media_player.socket" - ) as socket_class: + ) as remote_class: remote = mock.Mock() remote_class.return_value = remote - socket = mock.Mock() - socket_class.return_value = socket yield remote @@ -158,6 +162,7 @@ def wakeonlan_fixture(): with patch( "homeassistant.components.samsungtv.media_player.wakeonlan" ) as wakeonlan_module: + wakeonlan_module.BROADCAST_IP = wakeonlan.BROADCAST_IP yield wakeonlan_module @@ -169,7 +174,7 @@ def mock_now(): async def setup_samsungtv(hass, config): """Set up mock Samsung TV.""" - await async_setup_component(hass, "media_player", config) + await samsungtv.async_setup(hass, config) await hass.async_block_till_done() @@ -181,9 +186,15 @@ async def test_setup_with_mac(hass, remote): async def test_setup_duplicate(hass, remote, caplog): """Test duplicate setup of platform.""" - DUPLICATE = {DOMAIN: [MOCK_CONFIG[DOMAIN], MOCK_CONFIG[DOMAIN]]} + DUPLICATE = { + SAMSUNGTV_DOMAIN: [ + MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], + MOCK_CONFIG[SAMSUNGTV_DOMAIN][0], + ] + } await setup_samsungtv(hass, DUPLICATE) - assert "Ignoring duplicate Samsung TV fake" in caplog.text + assert hass.states.get(ENTITY_ID) + assert len(hass.states.async_all()) == 1 async def test_setup_without_mac(hass, remote): @@ -192,40 +203,6 @@ async def test_setup_without_mac(hass, remote): assert hass.states.get(ENTITY_ID_NOMAC) -async def test_setup_discovery(hass, remote): - """Test setup of platform with discovery.""" - hass.async_create_task( - async_load_platform( - hass, DOMAIN, SAMSUNGTV_DOMAIN, MOCK_CONFIG_DISCOVERY, {DOMAIN: {}} - ) - ) - await hass.async_block_till_done() - state = hass.states.get(ENTITY_ID_DISCOVERY) - assert state - assert state.name == "fake_discovery (fake_model)" - entity_registry = await hass.helpers.entity_registry.async_get_registry() - entry = entity_registry.async_get(ENTITY_ID_DISCOVERY) - assert entry - assert entry.unique_id == "fake_uuid" - - -async def test_setup_discovery_prefix(hass, remote): - """Test setup of platform with discovery.""" - hass.async_create_task( - async_load_platform( - hass, DOMAIN, SAMSUNGTV_DOMAIN, MOCK_CONFIG_DISCOVERY_PREFIX, {DOMAIN: {}} - ) - ) - await hass.async_block_till_done() - state = hass.states.get(ENTITY_ID_DISCOVERY_PREFIX) - assert state - assert state.name == "fake_discovery_prefix (fake_model_prefix)" - entity_registry = await hass.helpers.entity_registry.async_get_registry() - entry = entity_registry.async_get(ENTITY_ID_DISCOVERY_PREFIX) - assert entry - assert entry.unique_id == "fake_uuid_prefix" - - async def test_update_on(hass, remote, mock_now): """Testing update tv on.""" await setup_samsungtv(hass, MOCK_CONFIG) @@ -268,9 +245,9 @@ async def test_send_key(hass, remote, wakeonlan): async def test_send_key_autodetect_websocket(hass, remote): """Test for send key with autodetection of protocol.""" - with patch( - "homeassistant.components.samsungtv.media_player.SamsungRemote" - ) as remote, patch("homeassistant.components.samsungtv.media_player.socket"): + with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( + "homeassistant.components.samsungtv.config_flow.Remote" + ), patch("homeassistant.components.samsungtv.media_player.SamsungRemote") as remote: await setup_samsungtv(hass, MOCK_CONFIG_AUTO) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID_AUTO}, True @@ -284,10 +261,12 @@ async def test_send_key_autodetect_websocket(hass, remote): async def test_send_key_autodetect_websocket_exception(hass, caplog): """Test for send key with autodetection of protocol.""" caplog.set_level(logging.DEBUG) - with patch( + with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( + "homeassistant.components.samsungtv.config_flow.Remote" + ), patch( "homeassistant.components.samsungtv.media_player.SamsungRemote", side_effect=[exceptions.AccessDenied("Boom"), mock.DEFAULT], - ) as remote, patch("homeassistant.components.samsungtv.media_player.socket"): + ) as remote: await setup_samsungtv(hass, MOCK_CONFIG_AUTO) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID_AUTO}, True @@ -306,10 +285,12 @@ async def test_send_key_autodetect_websocket_exception(hass, caplog): async def test_send_key_autodetect_legacy(hass, remote): """Test for send key with autodetection of protocol.""" - with patch( + with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( + "homeassistant.components.samsungtv.config_flow.Remote" + ), patch( "homeassistant.components.samsungtv.media_player.SamsungRemote", side_effect=[OSError("Boom"), mock.DEFAULT], - ) as remote, patch("homeassistant.components.samsungtv.media_player.socket"): + ) as remote: await setup_samsungtv(hass, MOCK_CONFIG_AUTO) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID_AUTO}, True @@ -325,10 +306,12 @@ async def test_send_key_autodetect_legacy(hass, remote): async def test_send_key_autodetect_none(hass, remote): """Test for send key with autodetection of protocol.""" - with patch( + with patch("homeassistant.components.samsungtv.config_flow.socket"), patch( + "homeassistant.components.samsungtv.config_flow.Remote" + ), patch( "homeassistant.components.samsungtv.media_player.SamsungRemote", side_effect=OSError("Boom"), - ) as remote, patch("homeassistant.components.samsungtv.media_player.socket"): + ) as remote: await setup_samsungtv(hass, MOCK_CONFIG_AUTO) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID_AUTO}, True