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

pull/86292/head
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,
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(),

View File

@ -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

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)
== "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):