diff --git a/homeassistant/components/homewizard/coordinator.py b/homeassistant/components/homewizard/coordinator.py index 4f003da32bb..2da618eeb27 100644 --- a/homeassistant/components/homewizard/coordinator.py +++ b/homeassistant/components/homewizard/coordinator.py @@ -28,16 +28,13 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry] entry: ConfigEntry, host: str, ) -> None: - """Initialize Update Coordinator.""" - + """Initialize update coordinator.""" super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) self.entry = entry self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass)) async def _async_update_data(self) -> DeviceResponseEntry: """Fetch all device and sensor data from api.""" - - # Update all properties try: data = DeviceResponseEntry( device=await self.api.device(), diff --git a/homeassistant/components/homewizard/switch.py b/homeassistant/components/homewizard/switch.py index 1f76940fb1b..498beb7ebe4 100644 --- a/homeassistant/components/homewizard/switch.py +++ b/homeassistant/components/homewizard/switch.py @@ -1,20 +1,81 @@ -"""Creates Homewizard Energy switch entities.""" +"""Creates HomeWizard Energy switch entities.""" from __future__ import annotations +from collections.abc import Awaitable, Callable +from dataclasses import dataclass from typing import Any -from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity +from homewizard_energy import HomeWizardEnergy + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) 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 .const import DOMAIN +from .const import DOMAIN, DeviceResponseEntry from .coordinator import HWEnergyDeviceUpdateCoordinator from .entity import HomeWizardEntity from .helpers import homewizard_exception_handler +@dataclass +class HomeWizardEntityDescriptionMixin: + """Mixin values for HomeWizard entities.""" + + create_fn: Callable[[DeviceResponseEntry], bool] + available_fn: Callable[[DeviceResponseEntry], bool] + is_on_fn: Callable[[DeviceResponseEntry], bool | None] + set_fn: Callable[[HomeWizardEnergy, bool], Awaitable[Any]] + + +@dataclass +class HomeWizardSwitchEntityDescription( + SwitchEntityDescription, HomeWizardEntityDescriptionMixin +): + """Class describing HomeWizard switch entities.""" + + icon_off: str | None = None + + +SWITCHES = [ + HomeWizardSwitchEntityDescription( + key="power_on", + device_class=SwitchDeviceClass.OUTLET, + create_fn=lambda data: data.state is not None, + available_fn=lambda data: data.state is not None and not data.state.switch_lock, + is_on_fn=lambda data: data.state.power_on if data.state else None, + set_fn=lambda api, active: api.state_set(power_on=active), + ), + HomeWizardSwitchEntityDescription( + key="switch_lock", + name="Switch lock", + entity_category=EntityCategory.CONFIG, + icon="mdi:lock", + icon_off="mdi:lock-open", + create_fn=lambda data: data.state is not None, + available_fn=lambda data: data.state is not None, + is_on_fn=lambda data: data.state.switch_lock if data.state else None, + set_fn=lambda api, active: api.state_set(switch_lock=active), + ), + HomeWizardSwitchEntityDescription( + key="cloud_connection", + name="Cloud connection", + entity_category=EntityCategory.CONFIG, + icon="mdi:cloud", + icon_off="mdi:cloud-off-outline", + create_fn=lambda data: data.system is not None, + available_fn=lambda data: data.system is not None, + is_on_fn=lambda data: data.system.cloud_enabled if data.system else None, + set_fn=lambda api, active: api.system_set(cloud_enabled=active), + ), +] + + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -23,157 +84,60 @@ async def async_setup_entry( """Set up switches.""" coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - entities: list[SwitchEntity] = [] - - if coordinator.data.state: - entities.append(HWEnergyMainSwitchEntity(coordinator, entry)) - entities.append(HWEnergySwitchLockEntity(coordinator, entry)) - - if coordinator.data.system: - entities.append(HWEnergyEnableCloudEntity(hass, coordinator, entry)) - - async_add_entities(entities) + async_add_entities( + HomeWizardSwitchEntity( + coordinator=coordinator, + description=description, + entry=entry, + ) + for description in SWITCHES + if description.available_fn(coordinator.data) + ) -class HWEnergySwitchEntity(HomeWizardEntity, SwitchEntity): - """Representation switchable entity.""" +class HomeWizardSwitchEntity(HomeWizardEntity, SwitchEntity): + """Representation of a HomeWizard switch.""" + + entity_description: HomeWizardSwitchEntityDescription def __init__( self, coordinator: HWEnergyDeviceUpdateCoordinator, + description: HomeWizardSwitchEntityDescription, entry: ConfigEntry, - key: str, ) -> None: """Initialize the switch.""" super().__init__(coordinator) - self._attr_unique_id = f"{entry.unique_id}_{key}" + self.entity_description = description + self._attr_unique_id = f"{entry.unique_id}_{description.key}" + @property + def icon(self) -> str | None: + """Return the icon.""" + if self.entity_description.icon_off and self.is_on is False: + return self.entity_description.icon_off + return super().icon -class HWEnergyMainSwitchEntity(HWEnergySwitchEntity): - """Representation of the main power switch.""" + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self.entity_description.available_fn( + self.coordinator.data + ) - _attr_device_class = SwitchDeviceClass.OUTLET - - def __init__( - self, coordinator: HWEnergyDeviceUpdateCoordinator, entry: ConfigEntry - ) -> None: - """Initialize the switch.""" - super().__init__(coordinator, entry, "power_on") + @property + def is_on(self) -> bool | None: + """Return state of the switch.""" + return self.entity_description.is_on_fn(self.coordinator.data) @homewizard_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" - await self.coordinator.api.state_set(power_on=True) + await self.entity_description.set_fn(self.coordinator.api, True) await self.coordinator.async_refresh() @homewizard_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" - await self.coordinator.api.state_set(power_on=False) + await self.entity_description.set_fn(self.coordinator.api, False) await self.coordinator.async_refresh() - - @property - def available(self) -> bool: - """ - Return availability of power_on. - - This switch becomes unavailable when switch_lock is enabled. - """ - return ( - super().available - and self.coordinator.data.state is not None - and not self.coordinator.data.state.switch_lock - ) - - @property - def is_on(self) -> bool | None: - """Return true if switch is on.""" - if self.coordinator.data.state is None: - return None - return self.coordinator.data.state.power_on - - -class HWEnergySwitchLockEntity(HWEnergySwitchEntity): - """ - Representation of the switch-lock configuration. - - Switch-lock is a feature that forces the relay in 'on' state. - It disables any method that can turn of the relay. - """ - - _attr_name = "Switch lock" - _attr_device_class = SwitchDeviceClass.SWITCH - _attr_entity_category = EntityCategory.CONFIG - - def __init__( - self, coordinator: HWEnergyDeviceUpdateCoordinator, entry: ConfigEntry - ) -> None: - """Initialize the switch.""" - super().__init__(coordinator, entry, "switch_lock") - - @homewizard_exception_handler - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn switch-lock on.""" - await self.coordinator.api.state_set(switch_lock=True) - await self.coordinator.async_refresh() - - @homewizard_exception_handler - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn switch-lock off.""" - await self.coordinator.api.state_set(switch_lock=False) - await self.coordinator.async_refresh() - - @property - def is_on(self) -> bool | None: - """Return true if switch is on.""" - if self.coordinator.data.state is None: - return None - return self.coordinator.data.state.switch_lock - - -class HWEnergyEnableCloudEntity(HWEnergySwitchEntity): - """ - Representation of the enable cloud configuration. - - Turning off 'cloud connection' turns off all communication to HomeWizard Cloud. - At this point, the device is fully local. - """ - - _attr_name = "Cloud connection" - _attr_device_class = SwitchDeviceClass.SWITCH - _attr_entity_category = EntityCategory.CONFIG - - def __init__( - self, - hass: HomeAssistant, - coordinator: HWEnergyDeviceUpdateCoordinator, - entry: ConfigEntry, - ) -> None: - """Initialize the switch.""" - super().__init__(coordinator, entry, "cloud_connection") - self.hass = hass - self.entry = entry - - @homewizard_exception_handler - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn cloud connection on.""" - await self.coordinator.api.system_set(cloud_enabled=True) - await self.coordinator.async_refresh() - - @homewizard_exception_handler - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn cloud connection off.""" - await self.coordinator.api.system_set(cloud_enabled=False) - await self.coordinator.async_refresh() - - @property - def icon(self) -> str | None: - """Return the icon.""" - return "mdi:cloud" if self.is_on else "mdi:cloud-off-outline" - - @property - def is_on(self) -> bool | None: - """Return true if cloud connection is active.""" - if self.coordinator.data.system is None: - return None - return self.coordinator.data.system.cloud_enabled diff --git a/tests/components/homewizard/test_switch.py b/tests/components/homewizard/test_switch.py index 1826dc23fec..79e576a18d8 100644 --- a/tests/components/homewizard/test_switch.py +++ b/tests/components/homewizard/test_switch.py @@ -98,10 +98,8 @@ async def test_switch_loads_entities(hass, mock_config_entry_data, mock_config_e state_switch_lock.attributes.get(ATTR_FRIENDLY_NAME) == "Product Name (aabbccddeeff) Switch lock" ) - assert ( - state_switch_lock.attributes.get(ATTR_DEVICE_CLASS) == SwitchDeviceClass.SWITCH - ) - assert ATTR_ICON not in state_switch_lock.attributes + assert state_switch_lock.attributes.get(ATTR_ICON) == "mdi:lock-open" + assert ATTR_DEVICE_CLASS not in state_switch_lock.attributes async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_entry):