diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index 52776242911..acc6d5cd766 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -12,6 +12,7 @@ from samsungctl.exceptions import AccessDenied, ConnectionClosed, UnhandledRespo from samsungtvws.async_remote import SamsungTVWSAsyncRemote from samsungtvws.async_rest import SamsungTVAsyncRest from samsungtvws.command import SamsungTVCommand +from samsungtvws.event import MS_ERROR_EVENT from samsungtvws.exceptions import ConnectionFailure, HttpApiError from samsungtvws.remote import ChannelEmitCommand, SendRemoteKey from websockets.exceptions import ConnectionClosedError, WebSocketException @@ -460,7 +461,7 @@ class SamsungTVWSBridge(SamsungTVBridge): name=VALUE_CONF_NAME, ) try: - await self._remote.start_listening() + await self._remote.start_listening(self._remote_event) except ConnectionClosedError as err: # This is only happening when the auth was switched to DENY # A removed auth will lead to socket timeout because waiting @@ -498,6 +499,21 @@ class SamsungTVWSBridge(SamsungTVBridge): self._notify_new_token_callback() return self._remote + @staticmethod + def _remote_event(event: str, response: Any) -> None: + """Received event from remote websocket.""" + if event == MS_ERROR_EVENT: + # { 'event': 'ms.error', + # 'data': {'message': 'unrecognized method value : ms.remote.control'}} + if (data := response.get("data")) and ( + message := data.get("message") + ) == "unrecognized method value : ms.remote.control": + LOGGER.error( + "Your TV seems to be unsupported by " + "SamsungTVWSBridge and may need a PIN: '%s'", + message, + ) + async def async_power_off(self) -> None: """Send power off command to remote.""" if self._get_device_spec("FrameTVSupport") == "true": diff --git a/tests/components/samsungtv/conftest.py b/tests/components/samsungtv/conftest.py index d8bbfbf4627..c7733a3652a 100644 --- a/tests/components/samsungtv/conftest.py +++ b/tests/components/samsungtv/conftest.py @@ -1,5 +1,9 @@ """Fixtures for Samsung TV.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable from datetime import datetime +from typing import Any from unittest.mock import AsyncMock, Mock, patch import pytest @@ -48,14 +52,27 @@ def rest_api_fixture() -> Mock: @pytest.fixture(name="remotews") def remotews_fixture() -> Mock: """Patch the samsungtvws SamsungTVWS.""" + remotews = Mock(SamsungTVWSAsyncRemote) + remotews.__aenter__ = AsyncMock(return_value=remotews) + remotews.__aexit__ = AsyncMock() + remotews.app_list.return_value = SAMPLE_APP_LIST + remotews.token = "FAKE_TOKEN" + + def _start_listening( + ws_event_callback: Callable[[str, Any], Awaitable[None] | None] | None = None + ): + remotews.ws_event_callback = ws_event_callback + + def _mock_ws_event_callback(event: str, response: Any): + if remotews.ws_event_callback: + remotews.ws_event_callback(event, response) + + remotews.start_listening.side_effect = _start_listening + remotews.raise_mock_ws_event_callback = Mock(side_effect=_mock_ws_event_callback) + with patch( "homeassistant.components.samsungtv.bridge.SamsungTVWSAsyncRemote", ) as remotews_class: - remotews = Mock(SamsungTVWSAsyncRemote) - remotews.__aenter__ = AsyncMock(return_value=remotews) - remotews.__aexit__ = AsyncMock() - remotews.app_list.return_value = SAMPLE_APP_LIST - remotews.token = "FAKE_TOKEN" remotews_class.return_value = remotews yield remotews diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index c38dd2639b1..c76b9e9efb9 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -1066,3 +1066,40 @@ async def test_select_source_app(hass: HomeAssistant, remotews: Mock) -> None: assert len(commands) == 1 assert isinstance(commands[0], ChannelEmitCommand) assert commands[0].params["data"]["appId"] == "3201608010191" + + +async def test_websocket_unsupported_remote_control( + hass: HomeAssistant, remotews: Mock, caplog: pytest.LogCaptureFixture +) -> None: + """Test for turn_off.""" + with patch( + "homeassistant.components.samsungtv.bridge.Remote", + side_effect=[OSError("Boom"), DEFAULT_MOCK], + ): + await setup_samsungtv(hass, MOCK_CONFIGWS) + + remotews.send_command.reset_mock() + + assert await hass.services.async_call( + DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True + ) + remotews.raise_mock_ws_event_callback( + "ms.error", + { + "event": "ms.error", + "data": {"message": "unrecognized method value : ms.remote.control"}, + }, + ) + + # key called + assert remotews.send_command.call_count == 1 + commands = remotews.send_command.call_args_list[0].args[0] + assert len(commands) == 1 + assert isinstance(commands[0], SendRemoteKey) + assert commands[0].params["DataOfCmd"] == "KEY_POWER" + + # error logged + assert ( + "Your TV seems to be unsupported by SamsungTVWSBridge and may need a PIN: " + "'unrecognized method value : ms.remote.control'" in caplog.text + )