diff --git a/.coveragerc b/.coveragerc index 4be1a2f9dcf..edf8043e4f6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -823,6 +823,7 @@ omit = homeassistant/components/overkiz/scene.py homeassistant/components/overkiz/select.py homeassistant/components/overkiz/sensor.py + homeassistant/components/overkiz/switch.py homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/const.py homeassistant/components/ovo_energy/sensor.py diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 09eccdd30d9..21301143308 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -25,6 +25,7 @@ PLATFORMS: list[Platform] = [ Platform.NUMBER, Platform.SCENE, Platform.SENSOR, + Platform.SWITCH, ] IGNORED_OVERKIZ_DEVICES: list[UIClass | UIWidget] = [ @@ -35,5 +36,10 @@ IGNORED_OVERKIZ_DEVICES: list[UIClass | UIWidget] = [ # Used to map the Somfy widget and ui_class to the Home Assistant platform OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform] = { UIClass.DOOR_LOCK: Platform.LOCK, + UIWidget.DOMESTIC_HOT_WATER_TANK: Platform.SWITCH, # widgetName, uiClass is WaterHeatingSystem (not supported) UIClass.LIGHT: Platform.LIGHT, + UIClass.ON_OFF: Platform.SWITCH, + UIWidget.RTD_INDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) + UIWidget.RTD_OUTDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) + UIClass.SWIMMING_POOL: Platform.SWITCH, } diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index af67aea0f2a..b783cf8eac8 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -96,9 +96,11 @@ class OverkizDescriptiveEntity(OverkizEntity): """Initialize the device.""" super().__init__(device_url, coordinator) 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}" + if self.entity_description.name: + self._attr_name = f"{super().name} {self.entity_description.name}" + # Used by translations of state and select sensors @unique diff --git a/homeassistant/components/overkiz/switch.py b/homeassistant/components/overkiz/switch.py new file mode 100644 index 00000000000..95d96e6465c --- /dev/null +++ b/homeassistant/components/overkiz/switch.py @@ -0,0 +1,138 @@ +"""Support for Overkiz switches.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState +from pyoverkiz.enums.ui import UIClass, UIWidget +from pyoverkiz.types import StateType as OverkizStateType + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import HomeAssistantOverkizData +from .const import DOMAIN +from .entity import OverkizDescriptiveEntity + + +@dataclass +class OverkizSwitchDescriptionMixin: + """Define an entity description mixin for switch entities.""" + + turn_on: Callable[[Callable[..., Awaitable[None]]], Awaitable[None]] + turn_off: Callable[[Callable[..., Awaitable[None]]], Awaitable[None]] + is_on: Callable[[Callable[[str], OverkizStateType]], bool] + + +@dataclass +class OverkizSwitchDescription(SwitchEntityDescription, OverkizSwitchDescriptionMixin): + """Class to describe an Overkiz switch.""" + + +SWITCH_DESCRIPTIONS: list[OverkizSwitchDescription] = [ + OverkizSwitchDescription( + key=UIWidget.DOMESTIC_HOT_WATER_TANK, + turn_on=lambda execute_command: execute_command( + OverkizCommand.SET_FORCE_HEATING, OverkizCommandParam.ON + ), + turn_off=lambda execute_command: execute_command( + OverkizCommand.SET_FORCE_HEATING, OverkizCommandParam.OFF + ), + is_on=lambda select_state: ( + select_state(OverkizState.IO_FORCE_HEATING) == OverkizCommandParam.ON + ), + icon="mdi:water-boiler", + ), + OverkizSwitchDescription( + key=UIClass.ON_OFF, + turn_on=lambda execute_command: execute_command(OverkizCommand.ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), + is_on=lambda select_state: ( + select_state(OverkizState.CORE_ON_OFF) == OverkizCommandParam.ON + ), + device_class=SwitchDeviceClass.OUTLET, + ), + OverkizSwitchDescription( + key=UIClass.SWIMMING_POOL, + turn_on=lambda execute_command: execute_command(OverkizCommand.ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), + is_on=lambda select_state: ( + select_state(OverkizState.CORE_ON_OFF) == OverkizCommandParam.ON + ), + icon="mdi:pool", + ), + OverkizSwitchDescription( + key=UIWidget.RTD_INDOOR_SIREN, + turn_on=lambda execute_command: execute_command(OverkizCommand.ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), + is_on=lambda select_state: ( + select_state(OverkizState.CORE_ON_OFF) == OverkizCommandParam.ON + ), + icon="mdi:bell", + ), + OverkizSwitchDescription( + key=UIWidget.RTD_OUTDOOR_SIREN, + turn_on=lambda execute_command: execute_command(OverkizCommand.ON), + turn_off=lambda execute_command: execute_command(OverkizCommand.OFF), + is_on=lambda select_state: ( + select_state(OverkizState.CORE_ON_OFF) == OverkizCommandParam.ON + ), + icon="mdi:bell", + ), +] + +SUPPORTED_DEVICES = { + description.key: description for description in SWITCH_DESCRIPTIONS +} + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the Overkiz switch from a config entry.""" + data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + entities: list[OverkizSwitch] = [] + + for device in data.platforms[Platform.SWITCH]: + if description := SUPPORTED_DEVICES.get(device.widget) or SUPPORTED_DEVICES.get( + device.ui_class + ): + entities.append( + OverkizSwitch( + device.device_url, + data.coordinator, + description, + ) + ) + + async_add_entities(entities) + + +class OverkizSwitch(OverkizDescriptiveEntity, SwitchEntity): + """Representation of an Overkiz Switch.""" + + entity_description: OverkizSwitchDescription + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return self.entity_description.is_on(self.executor.select_state) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self.entity_description.turn_on(self.executor.async_execute_command) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self.entity_description.turn_off(self.executor.async_execute_command)