"""Support for Switchbot bot.""" from __future__ import annotations from collections.abc import Awaitable, Callable from dataclasses import dataclass import logging from typing import Any import switchbot from homeassistant.components.switch import ( SwitchDeviceClass, SwitchEntity, SwitchEntityDescription, ) from homeassistant.const import STATE_ON from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity from .const import AIRPURIFIER_BASIC_MODELS, AIRPURIFIER_TABLE_MODELS, DOMAIN from .coordinator import SwitchbotConfigEntry, SwitchbotDataUpdateCoordinator from .entity import SwitchbotSwitchedEntity, exception_handler @dataclass(frozen=True, kw_only=True) class SwitchbotSwitchEntityDescription(SwitchEntityDescription): """Describes a Switchbot switch entity.""" is_on_fn: Callable[[switchbot.SwitchbotDevice], bool | None] turn_on_fn: Callable[[switchbot.SwitchbotDevice], Awaitable[Any]] turn_off_fn: Callable[[switchbot.SwitchbotDevice], Awaitable[Any]] AIRPURIFIER_BASIC_SWITCHES: tuple[SwitchbotSwitchEntityDescription, ...] = ( SwitchbotSwitchEntityDescription( key="child_lock", translation_key="child_lock", device_class=SwitchDeviceClass.SWITCH, is_on_fn=lambda device: device.is_child_lock_on(), turn_on_fn=lambda device: device.open_child_lock(), turn_off_fn=lambda device: device.close_child_lock(), ), ) AIRPURIFIER_TABLE_SWITCHES: tuple[SwitchbotSwitchEntityDescription, ...] = ( *AIRPURIFIER_BASIC_SWITCHES, SwitchbotSwitchEntityDescription( key="wireless_charging", translation_key="wireless_charging", device_class=SwitchDeviceClass.SWITCH, is_on_fn=lambda device: device.is_wireless_charging_on(), turn_on_fn=lambda device: device.open_wireless_charging(), turn_off_fn=lambda device: device.close_wireless_charging(), ), ) PARALLEL_UPDATES = 0 _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, entry: SwitchbotConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Switchbot based on a config entry.""" coordinator = entry.runtime_data if isinstance(coordinator.device, switchbot.SwitchbotRelaySwitch2PM): entries = [ SwitchbotMultiChannelSwitch(coordinator, channel) for channel in range(1, coordinator.device.channel + 1) ] async_add_entities(entries) elif coordinator.model in AIRPURIFIER_BASIC_MODELS: async_add_entities( [ SwitchbotGenericSwitch(coordinator, desc) for desc in AIRPURIFIER_BASIC_SWITCHES ] ) elif coordinator.model in AIRPURIFIER_TABLE_MODELS: async_add_entities( [ SwitchbotGenericSwitch(coordinator, desc) for desc in AIRPURIFIER_TABLE_SWITCHES ] ) else: async_add_entities([SwitchBotSwitch(coordinator)]) class SwitchbotGenericSwitch(SwitchbotSwitchedEntity, SwitchEntity): """Representation of a Switchbot switch controlled via entity description.""" entity_description: SwitchbotSwitchEntityDescription _device: switchbot.SwitchbotDevice def __init__( self, coordinator: SwitchbotDataUpdateCoordinator, description: SwitchbotSwitchEntityDescription, ) -> None: """Initialize the Switchbot generic switch.""" super().__init__(coordinator) self.entity_description = description self._attr_unique_id = f"{coordinator.base_unique_id}-{description.key}" @property def is_on(self) -> bool | None: """Return true if device is on.""" return self.entity_description.is_on_fn(self._device) @exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn on.""" _LOGGER.debug( "Turning on %s for %s", self.entity_description.key, self._address ) await self.entity_description.turn_on_fn(self._device) self.async_write_ha_state() @exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off.""" _LOGGER.debug( "Turning off %s for %s", self.entity_description.key, self._address ) await self.entity_description.turn_off_fn(self._device) self.async_write_ha_state() class SwitchBotSwitch(SwitchbotSwitchedEntity, SwitchEntity, RestoreEntity): """Representation of a Switchbot switch.""" _attr_device_class = SwitchDeviceClass.SWITCH _attr_translation_key = "bot" _attr_name = None _device: switchbot.Switchbot def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None: """Initialize the Switchbot.""" super().__init__(coordinator) self._attr_is_on = False async def async_added_to_hass(self) -> None: """Run when entity about to be added.""" await super().async_added_to_hass() if not (last_state := await self.async_get_last_state()): return self._attr_is_on = last_state.state == STATE_ON self._last_run_success = last_state.attributes.get("last_run_success") @property def assumed_state(self) -> bool: """Return true if unable to access real state of entity.""" return not self._device.switch_mode() @property def is_on(self) -> bool | None: """Return true if device is on.""" if not self._device.switch_mode(): return self._attr_is_on return self._device.is_on() @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes.""" return { **super().extra_state_attributes, "switch_mode": self._device.switch_mode(), } class SwitchbotMultiChannelSwitch(SwitchbotSwitchedEntity, SwitchEntity): """Representation of a Switchbot multi-channel switch.""" _attr_device_class = SwitchDeviceClass.SWITCH _device: switchbot.Switchbot _attr_name = None def __init__( self, coordinator: SwitchbotDataUpdateCoordinator, channel: int ) -> None: """Initialize the Switchbot.""" super().__init__(coordinator) self._channel = channel self._attr_unique_id = f"{coordinator.base_unique_id}-{channel}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, f"{coordinator.base_unique_id}-channel-{channel}")}, manufacturer="SwitchBot", model_id="RelaySwitch2PM", name=f"{coordinator.device_name} Channel {channel}", ) @property def is_on(self) -> bool | None: """Return true if device is on.""" return self._device.is_on(self._channel) @exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn device on.""" _LOGGER.debug( "Turn Switchbot device on %s, channel %d", self._address, self._channel ) await self._device.turn_on(self._channel) self.async_write_ha_state() @exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn device off.""" _LOGGER.debug( "Turn Switchbot device off %s, channel %d", self._address, self._channel ) await self._device.turn_off(self._channel) self.async_write_ha_state()