From a2ce395fc66727e746e1f3bbec1221223a3bebdd Mon Sep 17 00:00:00 2001 From: Steve Easley Date: Sat, 12 Mar 2022 16:10:45 -0800 Subject: [PATCH] Add remote platform to Kaleidescape integration (#67959) --- .../components/kaleidescape/__init__.py | 2 +- .../components/kaleidescape/remote.py | 68 ++++++++ tests/components/kaleidescape/test_remote.py | 156 ++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/kaleidescape/remote.py create mode 100644 tests/components/kaleidescape/test_remote.py diff --git a/homeassistant/components/kaleidescape/__init__.py b/homeassistant/components/kaleidescape/__init__.py index 64205ecf838..a66ae25d436 100644 --- a/homeassistant/components/kaleidescape/__init__.py +++ b/homeassistant/components/kaleidescape/__init__.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.MEDIA_PLAYER, Platform.SENSOR] +PLATFORMS = [Platform.MEDIA_PLAYER, Platform.REMOTE, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/kaleidescape/remote.py b/homeassistant/components/kaleidescape/remote.py new file mode 100644 index 00000000000..61080052ee5 --- /dev/null +++ b/homeassistant/components/kaleidescape/remote.py @@ -0,0 +1,68 @@ +"""Sensor platform for Kaleidescape integration.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from kaleidescape import const as kaleidescape_const + +from homeassistant.components.remote import RemoteEntity +from homeassistant.exceptions import HomeAssistantError + +from .const import DOMAIN as KALEIDESCAPE_DOMAIN +from .entity import KaleidescapeEntity + +if TYPE_CHECKING: + from collections.abc import Iterable + from typing import Any + + from homeassistant.config_entries import ConfigEntry + from homeassistant.core import HomeAssistant + from homeassistant.helpers.entity_platform import AddEntitiesCallback + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the platform from a config entry.""" + entities = [KaleidescapeRemote(hass.data[KALEIDESCAPE_DOMAIN][entry.entry_id])] + async_add_entities(entities) + + +VALID_COMMANDS = { + "select", + "up", + "down", + "left", + "right", + "cancel", + "replay", + "scan_forward", + "scan_reverse", + "go_movie_covers", + "menu_toggle", +} + + +class KaleidescapeRemote(KaleidescapeEntity, RemoteEntity): + """Representation of a Kaleidescape device.""" + + @property + def is_on(self) -> bool: + """Return true if device is on.""" + return self._device.power.state == kaleidescape_const.DEVICE_POWER_STATE_ON + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the device on.""" + await self._device.leave_standby() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the device off.""" + await self._device.enter_standby() + + async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None: + """Send a command to a device.""" + for cmd in command: + if cmd not in VALID_COMMANDS: + raise HomeAssistantError(f"{cmd} is not a known command") + await getattr(self._device, cmd)() diff --git a/tests/components/kaleidescape/test_remote.py b/tests/components/kaleidescape/test_remote.py new file mode 100644 index 00000000000..3573d04395d --- /dev/null +++ b/tests/components/kaleidescape/test_remote.py @@ -0,0 +1,156 @@ +"""Tests for Kaleidescape remote platform.""" + +from unittest.mock import MagicMock + +import pytest + +from homeassistant.components.remote import ( + ATTR_COMMAND, + DOMAIN as REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, +) +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError + +from . import MOCK_SERIAL + +from tests.common import MockConfigEntry + +ENTITY_ID = f"remote.kaleidescape_device_{MOCK_SERIAL}" + + +async def test_entity( + hass: HomeAssistant, + mock_device: MagicMock, + mock_integration: MockConfigEntry, +) -> None: + """Test entity attributes.""" + assert hass.states.get(ENTITY_ID) + + +async def test_commands( + hass: HomeAssistant, + mock_device: MagicMock, + mock_integration: MockConfigEntry, +) -> None: + """Test service calls.""" + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) + assert mock_device.leave_standby.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_ID}, + blocking=True, + ) + assert mock_device.enter_standby.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["select"]}, + blocking=True, + ) + assert mock_device.select.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["up"]}, + blocking=True, + ) + assert mock_device.up.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["down"]}, + blocking=True, + ) + assert mock_device.down.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["left"]}, + blocking=True, + ) + assert mock_device.left.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["right"]}, + blocking=True, + ) + assert mock_device.right.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["cancel"]}, + blocking=True, + ) + assert mock_device.cancel.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["replay"]}, + blocking=True, + ) + assert mock_device.replay.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["scan_forward"]}, + blocking=True, + ) + assert mock_device.scan_forward.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["scan_reverse"]}, + blocking=True, + ) + assert mock_device.scan_reverse.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["go_movie_covers"]}, + blocking=True, + ) + assert mock_device.go_movie_covers.call_count == 1 + + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["menu_toggle"]}, + blocking=True, + ) + assert mock_device.menu_toggle.call_count == 1 + + +async def test_unknown_command( + hass: HomeAssistant, + mock_device: MagicMock, + mock_integration: MockConfigEntry, +) -> None: + """Test service calls.""" + with pytest.raises(HomeAssistantError) as err: + await hass.services.async_call( + REMOTE_DOMAIN, + SERVICE_SEND_COMMAND, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_COMMAND: ["bad"]}, + blocking=True, + ) + assert str(err.value) == "bad is not a known command"