Refactor switch for vesync (#134409)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
cdnninja 2025-02-05 09:07:35 -07:00 committed by GitHub
parent 078996effd
commit 4694240cfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 86 additions and 49 deletions

View File

@ -4,6 +4,8 @@ import logging
from pyvesync import VeSync
from pyvesync.vesyncbasedevice import VeSyncBaseDevice
from pyvesync.vesyncoutlet import VeSyncOutlet
from pyvesync.vesyncswitch import VeSyncWallSwitch
from homeassistant.core import HomeAssistant
@ -54,3 +56,15 @@ def is_humidifier(device: VeSyncBaseDevice) -> bool:
"""Check if the device represents a humidifier."""
return isinstance(device, VeSyncHumidifierDevice)
def is_outlet(device: VeSyncBaseDevice) -> bool:
"""Check if the device represents an outlet."""
return isinstance(device, VeSyncOutlet)
def is_wall_switch(device: VeSyncBaseDevice) -> bool:
"""Check if the device represents a wall switch, note this doessn't include dimming switches."""
return isinstance(device, VeSyncWallSwitch)

View File

@ -1,29 +1,59 @@
"""Support for VeSync switches."""
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any
from typing import Any, Final
from pyvesync.vesyncbasedevice import VeSyncBaseDevice
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DEV_TYPE_TO_HA, DOMAIN, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY
from .common import is_outlet, is_wall_switch
from .const import DOMAIN, VS_COORDINATOR, VS_DEVICES, VS_DISCOVERY
from .coordinator import VeSyncDataCoordinator
from .entity import VeSyncBaseEntity
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True, kw_only=True)
class VeSyncSwitchEntityDescription(SwitchEntityDescription):
"""A class that describes custom switch entities."""
is_on: Callable[[VeSyncBaseDevice], bool]
exists_fn: Callable[[VeSyncBaseDevice], bool]
on_fn: Callable[[VeSyncBaseDevice], bool]
off_fn: Callable[[VeSyncBaseDevice], bool]
SENSOR_DESCRIPTIONS: Final[tuple[VeSyncSwitchEntityDescription, ...]] = (
VeSyncSwitchEntityDescription(
key="device_status",
is_on=lambda device: device.device_status == "on",
# Other types of wall switches support dimming. Those use light.py platform.
exists_fn=lambda device: is_wall_switch(device) or is_outlet(device),
name=None,
on_fn=lambda device: device.turn_on(),
off_fn=lambda device: device.turn_off(),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switches."""
"""Set up switch platform."""
coordinator = hass.data[DOMAIN][VS_COORDINATOR]
@ -45,55 +75,46 @@ def _setup_entities(
async_add_entities,
coordinator: VeSyncDataCoordinator,
):
"""Check if device is a switch and add entity."""
entities: list[VeSyncBaseSwitch] = []
for dev in devices:
if DEV_TYPE_TO_HA.get(dev.device_type) == "outlet":
entities.append(VeSyncSwitchHA(dev, coordinator))
elif DEV_TYPE_TO_HA.get(dev.device_type) == "switch":
entities.append(VeSyncLightSwitch(dev, coordinator))
async_add_entities(entities, update_before_add=True)
"""Check if device is online and add entity."""
async_add_entities(
VeSyncSwitchEntity(dev, description, coordinator)
for dev in devices
for description in SENSOR_DESCRIPTIONS
if description.exists_fn(dev)
)
class VeSyncBaseSwitch(VeSyncBaseEntity, SwitchEntity):
"""Base class for VeSync switch Device Representations."""
class VeSyncSwitchEntity(SwitchEntity, VeSyncBaseEntity):
"""VeSync switch entity class."""
_attr_name = None
entity_description: VeSyncSwitchEntityDescription
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
self.device.turn_on()
def __init__(
self,
device: VeSyncBaseDevice,
description: VeSyncSwitchEntityDescription,
coordinator: VeSyncDataCoordinator,
) -> None:
"""Initialize the sensor."""
super().__init__(device, coordinator)
self.entity_description = description
self._attr_unique_id = f"{super().unique_id}-{description.key}"
if is_outlet(self.device):
self._attr_device_class = SwitchDeviceClass.OUTLET
elif is_wall_switch(self.device):
self._attr_device_class = SwitchDeviceClass.SWITCH
@property
def is_on(self) -> bool:
"""Return True if device is on."""
return self.device.device_status == "on"
def is_on(self) -> bool | None:
"""Return the entity value to represent the entity state."""
return self.entity_description.is_on(self.device)
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
self.device.turn_off()
"""Turn the entity off."""
if self.entity_description.off_fn(self.device):
self.schedule_update_ha_state()
class VeSyncSwitchHA(VeSyncBaseSwitch, SwitchEntity):
"""Representation of a VeSync switch."""
def __init__(
self, plug: VeSyncBaseDevice, coordinator: VeSyncDataCoordinator
) -> None:
"""Initialize the VeSync switch device."""
super().__init__(plug, coordinator)
self._attr_unique_id = f"{super().unique_id}-device_status"
self.smartplug = plug
class VeSyncLightSwitch(VeSyncBaseSwitch, SwitchEntity):
"""Handle representation of VeSync Light Switch."""
def __init__(
self, switch: VeSyncBaseDevice, coordinator: VeSyncDataCoordinator
) -> None:
"""Initialize Light Switch device class."""
super().__init__(switch, coordinator)
self._attr_unique_id = f"{super().unique_id}-device_status"
self.switch = switch
def turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
if self.entity_description.on_fn(self.device):
self.schedule_update_ha_state()

View File

@ -360,7 +360,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SwitchDeviceClass.OUTLET: 'outlet'>,
'original_icon': None,
'original_name': None,
'platform': 'vesync',
@ -375,6 +375,7 @@
# name: test_switch_state[Outlet][switch.outlet]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'outlet',
'friendly_name': 'Outlet',
}),
'context': <ANY>,
@ -518,7 +519,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SwitchDeviceClass.SWITCH: 'switch'>,
'original_icon': None,
'original_name': None,
'platform': 'vesync',
@ -533,6 +534,7 @@
# name: test_switch_state[Wall Switch][switch.wall_switch]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'switch',
'friendly_name': 'Wall Switch',
}),
'context': <ANY>,