Refactor HomeWizard switch platform to use entity descriptions (#86011)

This commit is contained in:
Franck Nijhof 2023-01-20 14:49:04 +01:00 committed by GitHub
parent a9728bd3a5
commit 7e8c081065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 142 deletions

View File

@ -28,16 +28,13 @@ class HWEnergyDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceResponseEntry]
entry: ConfigEntry, entry: ConfigEntry,
host: str, host: str,
) -> None: ) -> None:
"""Initialize Update Coordinator.""" """Initialize update coordinator."""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
self.entry = entry self.entry = entry
self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass)) self.api = HomeWizardEnergy(host, clientsession=async_get_clientsession(hass))
async def _async_update_data(self) -> DeviceResponseEntry: async def _async_update_data(self) -> DeviceResponseEntry:
"""Fetch all device and sensor data from api.""" """Fetch all device and sensor data from api."""
# Update all properties
try: try:
data = DeviceResponseEntry( data = DeviceResponseEntry(
device=await self.api.device(), device=await self.api.device(),

View File

@ -1,20 +1,81 @@
"""Creates Homewizard Energy switch entities.""" """Creates HomeWizard Energy switch entities."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any 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.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN, DeviceResponseEntry
from .coordinator import HWEnergyDeviceUpdateCoordinator from .coordinator import HWEnergyDeviceUpdateCoordinator
from .entity import HomeWizardEntity from .entity import HomeWizardEntity
from .helpers import homewizard_exception_handler 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( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
entry: ConfigEntry, entry: ConfigEntry,
@ -23,157 +84,60 @@ async def async_setup_entry(
"""Set up switches.""" """Set up switches."""
coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
entities: list[SwitchEntity] = [] async_add_entities(
HomeWizardSwitchEntity(
if coordinator.data.state: coordinator=coordinator,
entities.append(HWEnergyMainSwitchEntity(coordinator, entry)) description=description,
entities.append(HWEnergySwitchLockEntity(coordinator, entry)) entry=entry,
)
if coordinator.data.system: for description in SWITCHES
entities.append(HWEnergyEnableCloudEntity(hass, coordinator, entry)) if description.available_fn(coordinator.data)
)
async_add_entities(entities)
class HWEnergySwitchEntity(HomeWizardEntity, SwitchEntity): class HomeWizardSwitchEntity(HomeWizardEntity, SwitchEntity):
"""Representation switchable entity.""" """Representation of a HomeWizard switch."""
entity_description: HomeWizardSwitchEntityDescription
def __init__( def __init__(
self, self,
coordinator: HWEnergyDeviceUpdateCoordinator, coordinator: HWEnergyDeviceUpdateCoordinator,
description: HomeWizardSwitchEntityDescription,
entry: ConfigEntry, entry: ConfigEntry,
key: str,
) -> None: ) -> None:
"""Initialize the switch.""" """Initialize the switch."""
super().__init__(coordinator) 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): @property
"""Representation of the main power switch.""" 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 @property
def is_on(self) -> bool | None:
def __init__( """Return state of the switch."""
self, coordinator: HWEnergyDeviceUpdateCoordinator, entry: ConfigEntry return self.entity_description.is_on_fn(self.coordinator.data)
) -> None:
"""Initialize the switch."""
super().__init__(coordinator, entry, "power_on")
@homewizard_exception_handler @homewizard_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on.""" """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() await self.coordinator.async_refresh()
@homewizard_exception_handler @homewizard_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off.""" """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() 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

View File

@ -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) state_switch_lock.attributes.get(ATTR_FRIENDLY_NAME)
== "Product Name (aabbccddeeff) Switch lock" == "Product Name (aabbccddeeff) Switch lock"
) )
assert ( assert state_switch_lock.attributes.get(ATTR_ICON) == "mdi:lock-open"
state_switch_lock.attributes.get(ATTR_DEVICE_CLASS) == SwitchDeviceClass.SWITCH assert ATTR_DEVICE_CLASS not in state_switch_lock.attributes
)
assert ATTR_ICON not in state_switch_lock.attributes
async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_entry): async def test_switch_power_on_off(hass, mock_config_entry_data, mock_config_entry):