diff --git a/.coveragerc b/.coveragerc index 5b833dc4d6d..d0172075a84 100644 --- a/.coveragerc +++ b/.coveragerc @@ -818,6 +818,7 @@ omit = homeassistant/components/overkiz/lock.py homeassistant/components/overkiz/number.py homeassistant/components/overkiz/scene.py + homeassistant/components/overkiz/select.py homeassistant/components/overkiz/sensor.py homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/const.py diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index 2a0f529c502..af67aea0f2a 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -1,9 +1,12 @@ """Parent class for every Overkiz device.""" from __future__ import annotations +from enum import unique + from pyoverkiz.enums import OverkizAttribute, OverkizState from pyoverkiz.models import Device +from homeassistant.backports.enum import StrEnum from homeassistant.helpers.entity import DeviceInfo, EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -95,3 +98,12 @@ class OverkizDescriptiveEntity(OverkizEntity): self.entity_description = description self._attr_name = f"{super().name} {self.entity_description.name}" self._attr_unique_id = f"{super().unique_id}-{self.entity_description.key}" + + +# Used by translations of state and select sensors +@unique +class OverkizDeviceClass(StrEnum): + """Device class for Overkiz specific devices.""" + + OPEN_CLOSED_PEDESTRIAN = "overkiz__open_closed_pedestrian" + MEMORIZED_SIMPLE_VOLUME = "overkiz__memorized_simple_volume" diff --git a/homeassistant/components/overkiz/select.py b/homeassistant/components/overkiz/select.py new file mode 100644 index 00000000000..174a2553516 --- /dev/null +++ b/homeassistant/components/overkiz/select.py @@ -0,0 +1,134 @@ +"""Support for Overkiz select.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeAssistantOverkizData +from .const import DOMAIN, IGNORED_OVERKIZ_DEVICES +from .entity import OverkizDescriptiveEntity, OverkizDeviceClass + + +@dataclass +class OverkizSelectDescriptionMixin: + """Define an entity description mixin for select entities.""" + + options: list[str | OverkizCommandParam] + select_option: Callable[[str, Callable[..., Awaitable[None]]], Awaitable[None]] + + +@dataclass +class OverkizSelectDescription(SelectEntityDescription, OverkizSelectDescriptionMixin): + """Class to describe an Overkiz select entity.""" + + +def _select_option_open_closed_pedestrian( + option: str, execute_command: Callable[..., Awaitable[None]] +) -> Awaitable[None]: + """Change the selected option for Open/Closed/Pedestrian.""" + return execute_command( + { + OverkizCommandParam.CLOSED: OverkizCommand.CLOSE, + OverkizCommandParam.OPEN: OverkizCommand.OPEN, + OverkizCommandParam.PEDESTRIAN: OverkizCommand.SET_PEDESTRIAN_POSITION, + }[OverkizCommandParam(option)], + None, + ) + + +def _select_option_memorized_simple_volume( + option: str, execute_command: Callable[..., Awaitable[None]] +) -> Awaitable[None]: + """Change the selected option for Memorized Simple Volume.""" + return execute_command(OverkizCommand.SET_MEMORIZED_SIMPLE_VOLUME, option) + + +SELECT_DESCRIPTIONS: list[OverkizSelectDescription] = [ + OverkizSelectDescription( + key=OverkizState.CORE_OPEN_CLOSED_PEDESTRIAN, + name="Position", + icon="mdi:content-save-cog", + options=[ + OverkizCommandParam.OPEN, + OverkizCommandParam.PEDESTRIAN, + OverkizCommandParam.CLOSED, + ], + select_option=_select_option_open_closed_pedestrian, + device_class=OverkizDeviceClass.OPEN_CLOSED_PEDESTRIAN, + ), + OverkizSelectDescription( + key=OverkizState.IO_MEMORIZED_SIMPLE_VOLUME, + name="Memorized Simple Volume", + icon="mdi:volume-high", + options=[OverkizCommandParam.STANDARD, OverkizCommandParam.HIGHEST], + select_option=_select_option_memorized_simple_volume, + entity_category=EntityCategory.CONFIG, + device_class=OverkizDeviceClass.MEMORIZED_SIMPLE_VOLUME, + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Overkiz select from a config entry.""" + data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + entities: list[OverkizSelect] = [] + + key_supported_states = { + description.key: description for description in SELECT_DESCRIPTIONS + } + + for device in data.coordinator.data.values(): + if ( + device.widget in IGNORED_OVERKIZ_DEVICES + or device.ui_class in IGNORED_OVERKIZ_DEVICES + ): + continue + + for state in device.definition.states: + if description := key_supported_states.get(state.qualified_name): + entities.append( + OverkizSelect( + device.device_url, + data.coordinator, + description, + ) + ) + + async_add_entities(entities) + + +class OverkizSelect(OverkizDescriptiveEntity, SelectEntity): + """Representation of an Overkiz Select entity.""" + + entity_description: OverkizSelectDescription + + @property + def current_option(self) -> str | None: + """Return the selected entity option to represent the entity state.""" + if state := self.device.states.get(self.entity_description.key): + return str(state.value) + + return None + + @property + def options(self) -> list[str]: + """Return a set of selectable options.""" + return self.entity_description.options + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + await self.entity_description.select_option( + option, self.executor.async_execute_command + ) diff --git a/homeassistant/components/overkiz/strings.select.json b/homeassistant/components/overkiz/strings.select.json new file mode 100644 index 00000000000..02a47e47a05 --- /dev/null +++ b/homeassistant/components/overkiz/strings.select.json @@ -0,0 +1,13 @@ +{ + "state": { + "overkiz__open_closed_pedestrian": { + "open": "Open", + "pedestrian": "Pedestrian", + "closed": "Closed" + }, + "overkiz__memorized_simple_volume": { + "highest": "Highest", + "standard": "Standard" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/overkiz/translations/select.en.json b/homeassistant/components/overkiz/translations/select.en.json new file mode 100644 index 00000000000..c90ea528011 --- /dev/null +++ b/homeassistant/components/overkiz/translations/select.en.json @@ -0,0 +1,13 @@ +{ + "state": { + "overkiz__memorized_simple_volume": { + "highest": "Highest", + "standard": "Standard" + }, + "overkiz__open_closed_pedestrian": { + "closed": "Closed", + "open": "Open", + "pedestrian": "Pedestrian" + } + } +} \ No newline at end of file