From 45897b59f2a746656fe396ef0c127d05bb7e67de Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 22 May 2021 15:33:37 -0500 Subject: [PATCH] Turn on samsungtv with wakeonlan (#50964) If we have the mac address from discovery, we can use it to wake the TV. Currently the TV goes unavailable when you turn it off as the only way to turn it back on is wake on lan or via the remote. Users who are not using host networking can use a script instead. --- .../components/samsungtv/manifest.json | 3 +- .../components/samsungtv/media_player.py | 15 +++++++-- requirements_all.txt | 1 + requirements_test_all.txt | 1 + .../components/samsungtv/test_media_player.py | 32 +++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/samsungtv/manifest.json b/homeassistant/components/samsungtv/manifest.json index 4206aca7213..4ffe940f946 100644 --- a/homeassistant/components/samsungtv/manifest.json +++ b/homeassistant/components/samsungtv/manifest.json @@ -4,7 +4,8 @@ "documentation": "https://www.home-assistant.io/integrations/samsungtv", "requirements": [ "samsungctl[websocket]==0.7.1", - "samsungtvws==1.6.0" + "samsungtvws==1.6.0", + "wakeonlan==2.0.1" ], "ssdp": [ { diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 72e21ed205c..5822bafcc55 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -3,6 +3,7 @@ import asyncio from datetime import timedelta import voluptuous as vol +from wakeonlan import send_magic_packet from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerEntity from homeassistant.components.media_player.const import ( @@ -71,6 +72,7 @@ class SamsungTVDevice(MediaPlayerEntity): def __init__(self, bridge, config_entry, on_script): """Initialize the Samsung device.""" self._config_entry = config_entry + self._host = config_entry.data[CONF_HOST] self._mac = config_entry.data.get(CONF_MAC) self._manufacturer = config_entry.data.get(CONF_MANUFACTURER) self._model = config_entry.data.get(CONF_MODEL) @@ -146,7 +148,7 @@ class SamsungTVDevice(MediaPlayerEntity): """Return the availability of the device.""" if self._auth_failed: return False - return self._state == STATE_ON or self._on_script + return self._state == STATE_ON or self._on_script or self._mac @property def device_info(self): @@ -174,7 +176,7 @@ class SamsungTVDevice(MediaPlayerEntity): @property def supported_features(self): """Flag media player features that are supported.""" - if self._on_script: + if self._on_script or self._mac: return SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON return SUPPORT_SAMSUNGTV @@ -246,10 +248,19 @@ class SamsungTVDevice(MediaPlayerEntity): await asyncio.sleep(KEY_PRESS_TIMEOUT, self.hass.loop) await self.hass.async_add_executor_job(self.send_key, "KEY_ENTER") + def _wake_on_lan(self): + """Wake the device via wake on lan.""" + send_magic_packet(self._mac, ip_address=self._host) + # If the ip address changed since we last saw the device + # broadcast a packet as well + send_magic_packet(self._mac) + async def async_turn_on(self): """Turn the media player on.""" if self._on_script: await self._on_script.async_run(context=self._context) + elif self._mac: + await self.hass.async_add_executor_job(self._wake_on_lan) def select_source(self, source): """Select input source.""" diff --git a/requirements_all.txt b/requirements_all.txt index 656a4f7da8d..18365ca14cb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2325,6 +2325,7 @@ vtjp==0.1.14 # homeassistant.components.vultr vultr==0.1.2 +# homeassistant.components.samsungtv # homeassistant.components.wake_on_lan wakeonlan==2.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2688ffabed1..e90799028e8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1249,6 +1249,7 @@ vsure==1.7.3 # homeassistant.components.vultr vultr==0.1.2 +# homeassistant.components.samsungtv # homeassistant.components.wake_on_lan wakeonlan==2.0.1 diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index 0cf54e32807..02eceeaacb7 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -35,6 +35,7 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_HOST, CONF_IP_ADDRESS, + CONF_MAC, CONF_METHOD, CONF_NAME, CONF_PORT, @@ -98,6 +99,17 @@ MOCK_ENTRY_WS = { CONF_TOKEN: "123456789", } + +MOCK_ENTRY_WS_WITH_MAC = { + CONF_IP_ADDRESS: "test", + CONF_HOST: "fake_host", + CONF_METHOD: "websocket", + CONF_MAC: "aa:bb:cc:dd:ee:ff", + CONF_NAME: "fake", + CONF_PORT: 8002, + CONF_TOKEN: "123456789", +} + ENTITY_ID_NOTURNON = f"{DOMAIN}.fake_noturnon" MOCK_CONFIG_NOTURNON = { SAMSUNGTV_DOMAIN: [ @@ -593,6 +605,26 @@ async def test_turn_on_with_turnon(hass, remote, delay): assert delay.call_count == 1 +async def test_turn_on_wol(hass, remotews): + """Test turn on.""" + entry = MockConfigEntry( + domain=SAMSUNGTV_DOMAIN, + data=MOCK_ENTRY_WS_WITH_MAC, + unique_id="any", + ) + entry.add_to_hass(hass) + assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {}) + await hass.async_block_till_done() + with patch( + "homeassistant.components.samsungtv.media_player.send_magic_packet" + ) as mock_send_magic_packet: + assert await hass.services.async_call( + DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True + ) + await hass.async_block_till_done() + assert mock_send_magic_packet.called + + async def test_turn_on_without_turnon(hass, remote): """Test turn on.""" await setup_samsungtv(hass, MOCK_CONFIG_NOTURNON)