mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add ability to send custom keys to Samsung TV (#83439)
* Add SamsungTV Remote entity with support for turn-off and send command * Fix SamsungTV remote tests
This commit is contained in:
parent
a96215bf2e
commit
6c66af4e41
@ -49,7 +49,7 @@ from .const import (
|
|||||||
UPNP_SVC_RENDERING_CONTROL,
|
UPNP_SVC_RENDERING_CONTROL,
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE]
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import asyncio
|
|||||||
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
||||||
from collections.abc import Callable, Iterable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
import contextlib
|
import contextlib
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Generic, TypeVar, cast
|
from typing import Any, Generic, TypeVar, cast
|
||||||
|
|
||||||
from samsungctl import Remote
|
from samsungctl import Remote
|
||||||
@ -43,8 +44,10 @@ from homeassistant.const import (
|
|||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_component
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.device_registry import format_mac
|
from homeassistant.helpers.device_registry import format_mac
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_DESCRIPTION,
|
CONF_DESCRIPTION,
|
||||||
@ -67,6 +70,13 @@ from .const import (
|
|||||||
WEBSOCKET_PORTS,
|
WEBSOCKET_PORTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Since the TV will take a few seconds to go to sleep
|
||||||
|
# and actually be seen as off, we need to wait just a bit
|
||||||
|
# more than the next scan interval
|
||||||
|
SCAN_INTERVAL_PLUS_OFF_TIME = entity_component.DEFAULT_SCAN_INTERVAL + timedelta(
|
||||||
|
seconds=5
|
||||||
|
)
|
||||||
|
|
||||||
KEY_PRESS_TIMEOUT = 1.2
|
KEY_PRESS_TIMEOUT = 1.2
|
||||||
|
|
||||||
ENCRYPTED_MODEL_USES_POWER_OFF = {"H6400", "H6410"}
|
ENCRYPTED_MODEL_USES_POWER_OFF = {"H6400", "H6410"}
|
||||||
@ -161,6 +171,10 @@ class SamsungTVBridge(ABC):
|
|||||||
self._update_config_entry: Callable[[Mapping[str, Any]], None] | None = None
|
self._update_config_entry: Callable[[Mapping[str, Any]], None] | None = None
|
||||||
self._app_list_callback: Callable[[dict[str, str]], None] | None = None
|
self._app_list_callback: Callable[[dict[str, str]], None] | None = None
|
||||||
|
|
||||||
|
# Mark the end of a shutdown command (need to wait 15 seconds before
|
||||||
|
# sending the next command to avoid turning the TV back ON).
|
||||||
|
self._end_of_power_off: datetime | None = None
|
||||||
|
|
||||||
def register_reauth_callback(self, func: CALLBACK_TYPE) -> None:
|
def register_reauth_callback(self, func: CALLBACK_TYPE) -> None:
|
||||||
"""Register a callback function."""
|
"""Register a callback function."""
|
||||||
self._reauth_callback = func
|
self._reauth_callback = func
|
||||||
@ -203,8 +217,17 @@ class SamsungTVBridge(ABC):
|
|||||||
async def async_send_keys(self, keys: list[str]) -> None:
|
async def async_send_keys(self, keys: list[str]) -> None:
|
||||||
"""Send a list of keys to the tv."""
|
"""Send a list of keys to the tv."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power_off_in_progress(self) -> bool:
|
||||||
|
"""Return if power off has been recently requested."""
|
||||||
|
return (
|
||||||
|
self._end_of_power_off is not None
|
||||||
|
and self._end_of_power_off > dt_util.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
async def async_power_off(self) -> None:
|
async def async_power_off(self) -> None:
|
||||||
"""Send power off command to remote and close."""
|
"""Send power off command to remote and close."""
|
||||||
|
self._end_of_power_off = dt_util.utcnow() + SCAN_INTERVAL_PLUS_OFF_TIME
|
||||||
await self._async_send_power_off()
|
await self._async_send_power_off()
|
||||||
# Force closing of remote session to provide instant UI feedback
|
# Force closing of remote session to provide instant UI feedback
|
||||||
await self.async_close_remote()
|
await self.async_close_remote()
|
||||||
|
32
homeassistant/components/samsungtv/entity.py
Normal file
32
homeassistant/components/samsungtv/entity.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Base SamsungTV Entity."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_MAC, CONF_MODEL, CONF_NAME
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||||
|
|
||||||
|
from .bridge import SamsungTVBridge
|
||||||
|
from .const import CONF_MANUFACTURER, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
class SamsungTVEntity(Entity):
|
||||||
|
"""Defines a base SamsungTV entity."""
|
||||||
|
|
||||||
|
def __init__(self, *, bridge: SamsungTVBridge, config_entry: ConfigEntry) -> None:
|
||||||
|
"""Initialize the SamsungTV entity."""
|
||||||
|
self._bridge = bridge
|
||||||
|
self._mac = config_entry.data.get(CONF_MAC)
|
||||||
|
self._attr_name = config_entry.data.get(CONF_NAME)
|
||||||
|
self._attr_unique_id = config_entry.unique_id
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
name=self.name,
|
||||||
|
manufacturer=config_entry.data.get(CONF_MANUFACTURER),
|
||||||
|
model=config_entry.data.get(CONF_MODEL),
|
||||||
|
)
|
||||||
|
if self.unique_id:
|
||||||
|
self._attr_device_info["identifiers"] = {(DOMAIN, self.unique_id)}
|
||||||
|
if self._mac:
|
||||||
|
self._attr_device_info["connections"] = {
|
||||||
|
(dr.CONNECTION_NETWORK_MAC, self._mac)
|
||||||
|
}
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Coroutine, Sequence
|
from collections.abc import Coroutine, Sequence
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import async_timeout
|
import async_timeout
|
||||||
@ -31,26 +30,16 @@ from homeassistant.components.media_player import (
|
|||||||
MediaType,
|
MediaType,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_MODEL, CONF_NAME
|
from homeassistant.const import CONF_HOST
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import config_validation as cv
|
||||||
config_validation as cv,
|
|
||||||
device_registry as dr,
|
|
||||||
entity_component,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.trigger import PluggableAction
|
from homeassistant.helpers.trigger import PluggableAction
|
||||||
from homeassistant.util import dt as dt_util
|
|
||||||
|
|
||||||
from .bridge import SamsungTVBridge, SamsungTVWSBridge
|
from .bridge import SamsungTVBridge, SamsungTVWSBridge
|
||||||
from .const import (
|
from .const import CONF_SSDP_RENDERING_CONTROL_LOCATION, DOMAIN, LOGGER
|
||||||
CONF_MANUFACTURER,
|
from .entity import SamsungTVEntity
|
||||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
|
||||||
DOMAIN,
|
|
||||||
LOGGER,
|
|
||||||
)
|
|
||||||
from .triggers.turn_on import async_get_turn_on_trigger
|
from .triggers.turn_on import async_get_turn_on_trigger
|
||||||
|
|
||||||
SOURCES = {"TV": "KEY_TV", "HDMI": "KEY_HDMI"}
|
SOURCES = {"TV": "KEY_TV", "HDMI": "KEY_HDMI"}
|
||||||
@ -67,12 +56,6 @@ SUPPORT_SAMSUNGTV = (
|
|||||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||||
)
|
)
|
||||||
|
|
||||||
# Since the TV will take a few seconds to go to sleep
|
|
||||||
# and actually be seen as off, we need to wait just a bit
|
|
||||||
# more than the next scan interval
|
|
||||||
SCAN_INTERVAL_PLUS_OFF_TIME = entity_component.DEFAULT_SCAN_INTERVAL + timedelta(
|
|
||||||
seconds=5
|
|
||||||
)
|
|
||||||
|
|
||||||
# Max delay waiting for app_list to return, as some TVs simply ignore the request
|
# Max delay waiting for app_list to return, as some TVs simply ignore the request
|
||||||
APP_LIST_DELAY = 3
|
APP_LIST_DELAY = 3
|
||||||
@ -86,7 +69,7 @@ async def async_setup_entry(
|
|||||||
async_add_entities([SamsungTVDevice(bridge, entry)], True)
|
async_add_entities([SamsungTVDevice(bridge, entry)], True)
|
||||||
|
|
||||||
|
|
||||||
class SamsungTVDevice(MediaPlayerEntity):
|
class SamsungTVDevice(SamsungTVEntity, MediaPlayerEntity):
|
||||||
"""Representation of a Samsung TV."""
|
"""Representation of a Samsung TV."""
|
||||||
|
|
||||||
_attr_source_list: list[str]
|
_attr_source_list: list[str]
|
||||||
@ -97,9 +80,9 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the Samsung device."""
|
"""Initialize the Samsung device."""
|
||||||
|
super().__init__(bridge=bridge, config_entry=config_entry)
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
self._host: str | None = config_entry.data[CONF_HOST]
|
self._host: str | None = config_entry.data[CONF_HOST]
|
||||||
self._mac: str | None = config_entry.data.get(CONF_MAC)
|
|
||||||
self._ssdp_rendering_control_location: str | None = config_entry.data.get(
|
self._ssdp_rendering_control_location: str | None = config_entry.data.get(
|
||||||
CONF_SSDP_RENDERING_CONTROL_LOCATION
|
CONF_SSDP_RENDERING_CONTROL_LOCATION
|
||||||
)
|
)
|
||||||
@ -107,8 +90,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
# Assume that the TV is in Play mode
|
# Assume that the TV is in Play mode
|
||||||
self._playing: bool = True
|
self._playing: bool = True
|
||||||
|
|
||||||
self._attr_name: str | None = config_entry.data.get(CONF_NAME)
|
|
||||||
self._attr_unique_id = config_entry.unique_id
|
|
||||||
self._attr_is_volume_muted: bool = False
|
self._attr_is_volume_muted: bool = False
|
||||||
self._attr_device_class = MediaPlayerDeviceClass.TV
|
self._attr_device_class = MediaPlayerDeviceClass.TV
|
||||||
self._attr_source_list = list(SOURCES)
|
self._attr_source_list = list(SOURCES)
|
||||||
@ -123,22 +104,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
if self._ssdp_rendering_control_location:
|
if self._ssdp_rendering_control_location:
|
||||||
self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_SET
|
self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_SET
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
|
||||||
name=self.name,
|
|
||||||
manufacturer=config_entry.data.get(CONF_MANUFACTURER),
|
|
||||||
model=config_entry.data.get(CONF_MODEL),
|
|
||||||
)
|
|
||||||
if self.unique_id:
|
|
||||||
self._attr_device_info["identifiers"] = {(DOMAIN, self.unique_id)}
|
|
||||||
if self._mac:
|
|
||||||
self._attr_device_info["connections"] = {
|
|
||||||
(dr.CONNECTION_NETWORK_MAC, self._mac)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mark the end of a shutdown command (need to wait 15 seconds before
|
|
||||||
# sending the next command to avoid turning the TV back ON).
|
|
||||||
self._end_of_power_off: datetime | None = None
|
|
||||||
self._bridge = bridge
|
|
||||||
self._auth_failed = False
|
self._auth_failed = False
|
||||||
self._bridge.register_reauth_callback(self.access_denied)
|
self._bridge.register_reauth_callback(self.access_denied)
|
||||||
self._bridge.register_app_list_callback(self._app_list_callback)
|
self._bridge.register_app_list_callback(self._app_list_callback)
|
||||||
@ -190,7 +155,7 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
if self._auth_failed or self.hass.is_stopping:
|
if self._auth_failed or self.hass.is_stopping:
|
||||||
return
|
return
|
||||||
old_state = self._attr_state
|
old_state = self._attr_state
|
||||||
if self._power_off_in_progress():
|
if self._bridge.power_off_in_progress:
|
||||||
self._attr_state = MediaPlayerState.OFF
|
self._attr_state = MediaPlayerState.OFF
|
||||||
else:
|
else:
|
||||||
self._attr_state = (
|
self._attr_state = (
|
||||||
@ -333,7 +298,7 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
|
|
||||||
async def _async_launch_app(self, app_id: str) -> None:
|
async def _async_launch_app(self, app_id: str) -> None:
|
||||||
"""Send launch_app to the tv."""
|
"""Send launch_app to the tv."""
|
||||||
if self._power_off_in_progress():
|
if self._bridge.power_off_in_progress:
|
||||||
LOGGER.info("TV is powering off, not sending launch_app command")
|
LOGGER.info("TV is powering off, not sending launch_app command")
|
||||||
return
|
return
|
||||||
assert isinstance(self._bridge, SamsungTVWSBridge)
|
assert isinstance(self._bridge, SamsungTVWSBridge)
|
||||||
@ -342,17 +307,11 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
async def _async_send_keys(self, keys: list[str]) -> None:
|
async def _async_send_keys(self, keys: list[str]) -> None:
|
||||||
"""Send a key to the tv and handles exceptions."""
|
"""Send a key to the tv and handles exceptions."""
|
||||||
assert keys
|
assert keys
|
||||||
if self._power_off_in_progress() and keys[0] != "KEY_POWEROFF":
|
if self._bridge.power_off_in_progress and keys[0] != "KEY_POWEROFF":
|
||||||
LOGGER.info("TV is powering off, not sending keys: %s", keys)
|
LOGGER.info("TV is powering off, not sending keys: %s", keys)
|
||||||
return
|
return
|
||||||
await self._bridge.async_send_keys(keys)
|
await self._bridge.async_send_keys(keys)
|
||||||
|
|
||||||
def _power_off_in_progress(self) -> bool:
|
|
||||||
return (
|
|
||||||
self._end_of_power_off is not None
|
|
||||||
and self._end_of_power_off > dt_util.utcnow()
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return the availability of the device."""
|
"""Return the availability of the device."""
|
||||||
@ -362,7 +321,7 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
self.state == MediaPlayerState.ON
|
self.state == MediaPlayerState.ON
|
||||||
or bool(self._turn_on)
|
or bool(self._turn_on)
|
||||||
or self._mac is not None
|
or self._mac is not None
|
||||||
or self._power_off_in_progress()
|
or self._bridge.power_off_in_progress
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
@ -378,7 +337,6 @@ class SamsungTVDevice(MediaPlayerEntity):
|
|||||||
|
|
||||||
async def async_turn_off(self) -> None:
|
async def async_turn_off(self) -> None:
|
||||||
"""Turn off media player."""
|
"""Turn off media player."""
|
||||||
self._end_of_power_off = dt_util.utcnow() + SCAN_INTERVAL_PLUS_OFF_TIME
|
|
||||||
await self._bridge.async_power_off()
|
await self._bridge.async_power_off()
|
||||||
|
|
||||||
async def async_set_volume_level(self, volume: float) -> None:
|
async def async_set_volume_level(self, volume: float) -> None:
|
||||||
|
45
homeassistant/components/samsungtv/remote.py
Normal file
45
homeassistant/components/samsungtv/remote.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""Support for the SamsungTV remote."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .const import DOMAIN, LOGGER
|
||||||
|
from .entity import SamsungTVEntity
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Samsung TV from a config entry."""
|
||||||
|
bridge = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
async_add_entities([SamsungTVRemote(bridge=bridge, config_entry=entry)], True)
|
||||||
|
|
||||||
|
|
||||||
|
class SamsungTVRemote(SamsungTVEntity, RemoteEntity):
|
||||||
|
"""Device that sends commands to a SamsungTV."""
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the device off."""
|
||||||
|
await self._bridge.async_power_off()
|
||||||
|
|
||||||
|
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
|
||||||
|
"""Send a command to a device.
|
||||||
|
|
||||||
|
Supported keys vary between models.
|
||||||
|
See https://github.com/jaruba/ha-samsungtv-tizen/blob/master/Key_codes.md
|
||||||
|
"""
|
||||||
|
if self._bridge.power_off_in_progress:
|
||||||
|
LOGGER.info("TV is powering off, not sending keys: %s", command)
|
||||||
|
return
|
||||||
|
|
||||||
|
num_repeats = kwargs[ATTR_NUM_REPEATS]
|
||||||
|
command_list = list(command)
|
||||||
|
|
||||||
|
for _ in range(num_repeats):
|
||||||
|
await self._bridge.async_send_keys(command_list)
|
93
tests/components/samsungtv/test_remote.py
Normal file
93
tests/components/samsungtv/test_remote.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
"""The tests for the SamsungTV remote platform."""
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from samsungtvws.encrypted.remote import SamsungTVEncryptedCommand
|
||||||
|
|
||||||
|
from homeassistant.components.remote import (
|
||||||
|
ATTR_COMMAND,
|
||||||
|
DOMAIN as REMOTE_DOMAIN,
|
||||||
|
SERVICE_SEND_COMMAND,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import setup_samsungtv_entry
|
||||||
|
from .test_media_player import MOCK_ENTRYDATA_ENCRYPTED_WS
|
||||||
|
|
||||||
|
ENTITY_ID = f"{REMOTE_DOMAIN}.fake"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("remoteencws", "rest_api")
|
||||||
|
async def test_setup(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setup with basic config."""
|
||||||
|
await setup_samsungtv_entry(hass, MOCK_ENTRYDATA_ENCRYPTED_WS)
|
||||||
|
assert hass.states.get(ENTITY_ID)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("remoteencws", "rest_api")
|
||||||
|
async def test_unique_id(hass: HomeAssistant) -> None:
|
||||||
|
"""Test unique id."""
|
||||||
|
await setup_samsungtv_entry(hass, MOCK_ENTRYDATA_ENCRYPTED_WS)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
main = entity_registry.async_get(ENTITY_ID)
|
||||||
|
assert main.unique_id == "any"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("remoteencws", "rest_api")
|
||||||
|
async def test_main_services(
|
||||||
|
hass: HomeAssistant, remoteencws: Mock, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""Test for turn_off."""
|
||||||
|
await setup_samsungtv_entry(hass, MOCK_ENTRYDATA_ENCRYPTED_WS)
|
||||||
|
|
||||||
|
remoteencws.send_commands.reset_mock()
|
||||||
|
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
REMOTE_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# key called
|
||||||
|
assert remoteencws.send_commands.call_count == 1
|
||||||
|
commands = remoteencws.send_commands.call_args_list[0].args[0]
|
||||||
|
assert len(commands) == 2
|
||||||
|
assert isinstance(command := commands[0], SamsungTVEncryptedCommand)
|
||||||
|
assert command.body["param3"] == "KEY_POWEROFF"
|
||||||
|
assert isinstance(command := commands[1], SamsungTVEncryptedCommand)
|
||||||
|
assert command.body["param3"] == "KEY_POWER"
|
||||||
|
|
||||||
|
# commands not sent : power off in progress
|
||||||
|
remoteencws.send_commands.reset_mock()
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
REMOTE_DOMAIN,
|
||||||
|
SERVICE_SEND_COMMAND,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["dash"]},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert "TV is powering off, not sending keys: ['dash']" in caplog.text
|
||||||
|
remoteencws.send_commands.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("remoteencws", "rest_api")
|
||||||
|
async def test_send_command_service(hass: HomeAssistant, remoteencws: Mock) -> None:
|
||||||
|
"""Test the send command."""
|
||||||
|
await setup_samsungtv_entry(hass, MOCK_ENTRYDATA_ENCRYPTED_WS)
|
||||||
|
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
REMOTE_DOMAIN,
|
||||||
|
SERVICE_SEND_COMMAND,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["dash"]},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert remoteencws.send_commands.call_count == 1
|
||||||
|
commands = remoteencws.send_commands.call_args_list[0].args[0]
|
||||||
|
assert len(commands) == 1
|
||||||
|
assert isinstance(command := commands[0], SamsungTVEncryptedCommand)
|
||||||
|
assert command.body["param3"] == "dash"
|
Loading…
x
Reference in New Issue
Block a user