Convert Shelly block switches to EntityDescription (#106985)

This commit is contained in:
Simone Chemelli 2025-03-05 14:56:30 +01:00 committed by GitHub
parent c0e5a549b6
commit 61e0b938ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 53 deletions

View File

@ -7,7 +7,7 @@ from dataclasses import dataclass
from typing import Any, cast from typing import Any, cast
from aioshelly.block_device import Block from aioshelly.block_device import Block
from aioshelly.const import MODEL_2, MODEL_25, RPC_GENERATIONS from aioshelly.const import RPC_GENERATIONS
from homeassistant.components.climate import DOMAIN as CLIMATE_PLATFORM from homeassistant.components.climate import DOMAIN as CLIMATE_PLATFORM
from homeassistant.components.switch import ( from homeassistant.components.switch import (
@ -21,12 +21,11 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.entity_registry import RegistryEntry
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from .const import CONF_SLEEP_PERIOD, MOTION_MODELS
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
from .entity import ( from .entity import (
BlockEntityDescription, BlockEntityDescription,
RpcEntityDescription, RpcEntityDescription,
ShellyBlockEntity, ShellyBlockAttributeEntity,
ShellyRpcAttributeEntity, ShellyRpcAttributeEntity,
ShellySleepingBlockAttributeEntity, ShellySleepingBlockAttributeEntity,
async_setup_entry_attribute_entities, async_setup_entry_attribute_entities,
@ -34,10 +33,9 @@ from .entity import (
) )
from .utils import ( from .utils import (
async_remove_orphaned_entities, async_remove_orphaned_entities,
async_remove_shelly_entity,
get_device_entry_gen, get_device_entry_gen,
get_virtual_component_ids, get_virtual_component_ids,
is_block_channel_type_light, is_block_exclude_from_relay,
is_rpc_exclude_from_relay, is_rpc_exclude_from_relay,
) )
@ -47,11 +45,20 @@ class BlockSwitchDescription(BlockEntityDescription, SwitchEntityDescription):
"""Class to describe a BLOCK switch.""" """Class to describe a BLOCK switch."""
MOTION_SWITCH = BlockSwitchDescription( BLOCK_RELAY_SWITCHES = {
key="sensor|motionActive", ("relay", "output"): BlockSwitchDescription(
name="Motion detection", key="relay|output",
entity_category=EntityCategory.CONFIG, removal_condition=is_block_exclude_from_relay,
) )
}
BLOCK_SLEEPING_MOTION_SWITCH = {
("sensor", "motionActive"): BlockSwitchDescription(
key="sensor|motionActive",
name="Motion detection",
entity_category=EntityCategory.CONFIG,
)
}
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -120,46 +127,17 @@ def async_setup_block_entry(
coordinator = config_entry.runtime_data.block coordinator = config_entry.runtime_data.block
assert coordinator assert coordinator
# Add Shelly Motion as a switch async_setup_entry_attribute_entities(
if coordinator.model in MOTION_MODELS: hass, config_entry, async_add_entities, BLOCK_RELAY_SWITCHES, BlockRelaySwitch
async_setup_entry_attribute_entities( )
hass,
config_entry,
async_add_entities,
{("sensor", "motionActive"): MOTION_SWITCH},
BlockSleepingMotionSwitch,
)
return
if config_entry.data[CONF_SLEEP_PERIOD]: async_setup_entry_attribute_entities(
return hass,
config_entry,
# In roller mode the relay blocks exist but do not contain required info async_add_entities,
if ( BLOCK_SLEEPING_MOTION_SWITCH,
coordinator.model in [MODEL_2, MODEL_25] BlockSleepingMotionSwitch,
and coordinator.device.settings["mode"] != "relay" )
):
return
relay_blocks = []
assert coordinator.device.blocks
for block in coordinator.device.blocks:
if block.type != "relay" or (
block.channel is not None
and is_block_channel_type_light(
coordinator.device.settings, int(block.channel)
)
):
continue
relay_blocks.append(block)
unique_id = f"{coordinator.mac}-{block.type}_{block.channel}"
async_remove_shelly_entity(hass, "light", unique_id)
if not relay_blocks:
return
async_add_entities(BlockRelaySwitch(coordinator, block) for block in relay_blocks)
@callback @callback
@ -265,13 +243,22 @@ class BlockSleepingMotionSwitch(
self.last_state = last_state self.last_state = last_state
class BlockRelaySwitch(ShellyBlockEntity, SwitchEntity): class BlockRelaySwitch(ShellyBlockAttributeEntity, SwitchEntity):
"""Entity that controls a relay on Block based Shelly devices.""" """Entity that controls a relay on Block based Shelly devices."""
def __init__(self, coordinator: ShellyBlockCoordinator, block: Block) -> None: entity_description: BlockSwitchDescription
def __init__(
self,
coordinator: ShellyBlockCoordinator,
block: Block,
attribute: str,
description: BlockSwitchDescription,
) -> None:
"""Initialize relay switch.""" """Initialize relay switch."""
super().__init__(coordinator, block) super().__init__(coordinator, block, attribute, description)
self.control_result: dict[str, Any] | None = None self.control_result: dict[str, Any] | None = None
self._attr_unique_id: str = f"{coordinator.mac}-{block.description}"
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View File

@ -6,7 +6,7 @@ from collections.abc import Iterable
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ipaddress import IPv4Address, IPv6Address, ip_address from ipaddress import IPv4Address, IPv6Address, ip_address
from types import MappingProxyType from types import MappingProxyType
from typing import Any, cast from typing import TYPE_CHECKING, Any, cast
from aiohttp.web import Request, WebSocketResponse from aiohttp.web import Request, WebSocketResponse
from aioshelly.block_device import COAP, Block, BlockDevice from aioshelly.block_device import COAP, Block, BlockDevice
@ -175,6 +175,18 @@ def is_block_momentary_input(
return button_type in momentary_types return button_type in momentary_types
def is_block_exclude_from_relay(settings: dict[str, Any], block: Block) -> bool:
"""Return true if block should be excluded from switch platform."""
if settings.get("mode") == "roller":
return True
if TYPE_CHECKING:
assert block.channel is not None
return is_block_channel_type_light(settings, int(block.channel))
def get_device_uptime(uptime: float, last_uptime: datetime | None) -> datetime: def get_device_uptime(uptime: float, last_uptime: datetime | None) -> datetime:
"""Return device uptime string, tolerate up to 5 seconds deviation.""" """Return device uptime string, tolerate up to 5 seconds deviation."""
delta_uptime = utcnow() - timedelta(seconds=uptime) delta_uptime = utcnow() - timedelta(seconds=uptime)