diff --git a/.coveragerc b/.coveragerc index 48c8425eed8..2113ea0c202 100644 --- a/.coveragerc +++ b/.coveragerc @@ -808,6 +808,7 @@ omit = homeassistant/components/overkiz/entity.py homeassistant/components/overkiz/executor.py homeassistant/components/overkiz/lock.py + homeassistant/components/overkiz/number.py homeassistant/components/overkiz/sensor.py homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/const.py diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 60591d9d761..f1d1d0fdb74 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -20,6 +20,7 @@ UPDATE_INTERVAL_ALL_ASSUMED_STATE: Final = timedelta(minutes=60) PLATFORMS: list[Platform] = [ Platform.BUTTON, Platform.LOCK, + Platform.NUMBER, Platform.SENSOR, ] diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index 0c931bc5985..b88417b0d2b 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -7,9 +7,8 @@ from dataclasses import dataclass from pyoverkiz.enums import OverkizAttribute, OverkizState from pyoverkiz.models import Device -from homeassistant.components.button import ButtonEntityDescription from homeassistant.components.sensor import SensorEntityDescription -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity import DeviceInfo, EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN @@ -100,7 +99,7 @@ class OverkizDescriptiveEntity(OverkizEntity): self, device_url: str, coordinator: OverkizDataUpdateCoordinator, - description: OverkizSensorDescription | ButtonEntityDescription, + description: EntityDescription, ) -> None: """Initialize the device.""" super().__init__(device_url, coordinator) diff --git a/homeassistant/components/overkiz/number.py b/homeassistant/components/overkiz/number.py new file mode 100644 index 00000000000..d13370207fa --- /dev/null +++ b/homeassistant/components/overkiz/number.py @@ -0,0 +1,103 @@ +"""Support for Overkiz (virtual) numbers.""" +from __future__ import annotations + +from dataclasses import dataclass + +from pyoverkiz.enums import OverkizCommand, OverkizState + +from homeassistant.components.number import NumberEntity, NumberEntityDescription +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 + + +@dataclass +class OverkizNumberDescriptionMixin: + """Define an entity description mixin for number entities.""" + + command: str + + +@dataclass +class OverkizNumberDescription(NumberEntityDescription, OverkizNumberDescriptionMixin): + """Class to describe an Overkiz number.""" + + +NUMBER_DESCRIPTIONS: list[OverkizNumberDescription] = [ + # Cover: My Position (0 - 100) + OverkizNumberDescription( + key=OverkizState.CORE_MEMORIZED_1_POSITION, + name="My Position", + icon="mdi:content-save-cog", + command=OverkizCommand.SET_MEMORIZED_1_POSITION, + entity_category=EntityCategory.CONFIG, + ), + # WaterHeater: Expected Number Of Shower (2 - 4) + OverkizNumberDescription( + key=OverkizState.CORE_EXPECTED_NUMBER_OF_SHOWER, + name="Expected Number Of Shower", + icon="mdi:shower-head", + command=OverkizCommand.SET_EXPECTED_NUMBER_OF_SHOWER, + min_value=2, + max_value=4, + entity_category=EntityCategory.CONFIG, + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +): + """Set up the Overkiz number from a config entry.""" + data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + entities: list[OverkizNumber] = [] + + key_supported_states = { + description.key: description for description in NUMBER_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( + OverkizNumber( + device.device_url, + data.coordinator, + description, + ) + ) + + async_add_entities(entities) + + +class OverkizNumber(OverkizDescriptiveEntity, NumberEntity): + """Representation of an Overkiz Number.""" + + entity_description: OverkizNumberDescription + + @property + def value(self) -> float: + """Return the entity value to represent the entity state.""" + if state := self.device.states.get(self.entity_description.key): + return state.value + + return 0 + + async def async_set_value(self, value: float) -> None: + """Set new value.""" + await self.executor.async_execute_command( + self.entity_description.command, value + )