From d565c1a84bf0b3df764b19837b43e774a6493e44 Mon Sep 17 00:00:00 2001 From: Steve Easley Date: Wed, 24 Apr 2024 11:36:50 -0400 Subject: [PATCH] Add select platform to jvc_projector component (#111638) * Initial commit of jvc_projector select platform * Move icon to icons.json * Apply suggestions from code review * Update tests/components/jvc_projector/test_select.py --------- Co-authored-by: Erik Montnemery Co-authored-by: Joost Lekkerkerker --- .../components/jvc_projector/__init__.py | 2 +- .../components/jvc_projector/icons.json | 5 ++ .../components/jvc_projector/select.py | 77 +++++++++++++++++++ .../components/jvc_projector/strings.json | 9 +++ tests/components/jvc_projector/test_select.py | 44 +++++++++++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/jvc_projector/select.py create mode 100644 tests/components/jvc_projector/test_select.py diff --git a/homeassistant/components/jvc_projector/__init__.py b/homeassistant/components/jvc_projector/__init__.py index 28e4cc995bb..8ce1fb46e3d 100644 --- a/homeassistant/components/jvc_projector/__init__.py +++ b/homeassistant/components/jvc_projector/__init__.py @@ -18,7 +18,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from .const import DOMAIN from .coordinator import JvcProjectorDataUpdateCoordinator -PLATFORMS = [Platform.BINARY_SENSOR, Platform.REMOTE, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.REMOTE, Platform.SELECT, Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/jvc_projector/icons.json b/homeassistant/components/jvc_projector/icons.json index c70ded78cb4..a0404b328e1 100644 --- a/homeassistant/components/jvc_projector/icons.json +++ b/homeassistant/components/jvc_projector/icons.json @@ -8,6 +8,11 @@ } } }, + "select": { + "input": { + "default": "mdi:hdmi-port" + } + }, "sensor": { "jvc_power_status": { "default": "mdi:power-plug-off", diff --git a/homeassistant/components/jvc_projector/select.py b/homeassistant/components/jvc_projector/select.py new file mode 100644 index 00000000000..1395637fad1 --- /dev/null +++ b/homeassistant/components/jvc_projector/select.py @@ -0,0 +1,77 @@ +"""Select platform for the jvc_projector integration.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Final + +from jvcprojector import JvcProjector, const + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import JvcProjectorDataUpdateCoordinator +from .const import DOMAIN +from .entity import JvcProjectorEntity + + +@dataclass(frozen=True, kw_only=True) +class JvcProjectorSelectDescription(SelectEntityDescription): + """Describes JVC Projector select entities.""" + + command: Callable[[JvcProjector, str], Awaitable[None]] + + +OPTIONS: Final[dict[str, dict[str, str]]] = { + "input": {const.HDMI1: const.REMOTE_HDMI_1, const.HDMI2: const.REMOTE_HDMI_2} +} + +SELECTS: Final[list[JvcProjectorSelectDescription]] = [ + JvcProjectorSelectDescription( + key="input", + translation_key="input", + options=list(OPTIONS["input"]), + command=lambda device, option: device.remote(OPTIONS["input"][option]), + ) +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the JVC Projector platform from a config entry.""" + coordinator: JvcProjectorDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + JvcProjectorSelectEntity(coordinator, description) for description in SELECTS + ) + + +class JvcProjectorSelectEntity(JvcProjectorEntity, SelectEntity): + """Representation of a JVC Projector select entity.""" + + entity_description: JvcProjectorSelectDescription + + def __init__( + self, + coordinator: JvcProjectorDataUpdateCoordinator, + description: JvcProjectorSelectDescription, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.unique_id}_{description.key}" + + @property + def current_option(self) -> str | None: + """Return the selected entity option to represent the entity state.""" + return self.coordinator.data[self.entity_description.key] + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + await self.entity_description.command(self.coordinator.device, option) diff --git a/homeassistant/components/jvc_projector/strings.json b/homeassistant/components/jvc_projector/strings.json index 9991fa1cf67..b89139cbab3 100644 --- a/homeassistant/components/jvc_projector/strings.json +++ b/homeassistant/components/jvc_projector/strings.json @@ -38,6 +38,15 @@ "name": "[%key:component::sensor::entity_component::power::name%]" } }, + "select": { + "input": { + "name": "Input", + "state": { + "hdmi1": "HDMI 1", + "hdmi2": "HDMI 2" + } + } + }, "sensor": { "jvc_power_status": { "name": "Power status", diff --git a/tests/components/jvc_projector/test_select.py b/tests/components/jvc_projector/test_select.py new file mode 100644 index 00000000000..a52133bd688 --- /dev/null +++ b/tests/components/jvc_projector/test_select.py @@ -0,0 +1,44 @@ +"""Tests for JVC Projector select platform.""" + +from unittest.mock import MagicMock + +from jvcprojector import const + +from homeassistant.components.select import ( + ATTR_OPTIONS, + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_OPTION +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry + +INPUT_ENTITY_ID = "select.jvc_projector_input" + + +async def test_input_select( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_device: MagicMock, + mock_integration: MockConfigEntry, +) -> None: + """Test input select.""" + entity = hass.states.get(INPUT_ENTITY_ID) + assert entity + assert entity.attributes.get(ATTR_FRIENDLY_NAME) == "JVC Projector Input" + assert entity.attributes.get(ATTR_OPTIONS) == [const.HDMI1, const.HDMI2] + assert entity.state == const.HDMI1 + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: INPUT_ENTITY_ID, + ATTR_OPTION: const.HDMI2, + }, + blocking=True, + ) + + mock_device.remote.assert_called_once_with(const.REMOTE_HDMI_2)