"""An abstract class common to all Switchbot entities.""" from __future__ import annotations from collections.abc import Mapping import logging from typing import Any from switchbot import Switchbot, SwitchbotDevice from homeassistant.components.bluetooth.passive_update_coordinator import ( PassiveBluetoothCoordinatorEntity, ) from homeassistant.const import ATTR_CONNECTIONS from homeassistant.core import callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo, ToggleEntity from .const import MANUFACTURER from .coordinator import SwitchbotDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) class SwitchbotEntity( PassiveBluetoothCoordinatorEntity[SwitchbotDataUpdateCoordinator] ): """Generic entity encapsulating common features of Switchbot device.""" _device: SwitchbotDevice _attr_has_entity_name = True def __init__(self, coordinator: SwitchbotDataUpdateCoordinator) -> None: """Initialize the entity.""" super().__init__(coordinator) self._device = coordinator.device self._last_run_success: bool | None = None self._address = coordinator.ble_device.address self._attr_unique_id = coordinator.base_unique_id self._attr_device_info = DeviceInfo( connections={(dr.CONNECTION_BLUETOOTH, self._address)}, manufacturer=MANUFACTURER, model=coordinator.model, # Sometimes the modelName is missing from the advertisement data name=coordinator.device_name, ) if ":" not in self._address: # MacOS Bluetooth addresses are not mac addresses return # If the bluetooth address is also a mac address, # add this connection as well to prevent a new device # entry from being created when upgrading from a previous # version of the integration. self._attr_device_info[ATTR_CONNECTIONS].add( (dr.CONNECTION_NETWORK_MAC, self._address) ) @property def parsed_data(self) -> dict[str, Any]: """Return parsed device data for this entity.""" return self.coordinator.device.parsed_data @property def extra_state_attributes(self) -> Mapping[Any, Any]: """Return the state attributes.""" return {"last_run_success": self._last_run_success} @callback def _async_update_attrs(self) -> None: """Update the entity attributes.""" @callback def _handle_coordinator_update(self) -> None: """Handle data update.""" self._async_update_attrs() self.async_write_ha_state() async def async_added_to_hass(self) -> None: """Register callbacks.""" self.async_on_remove(self._device.subscribe(self._handle_coordinator_update)) return await super().async_added_to_hass() async def async_update(self) -> None: """Update the entity. Only used by the generic entity update service. """ await self._device.update() class SwitchbotSwitchedEntity(SwitchbotEntity, ToggleEntity): """Base class for Switchbot entities that can be turned on and off.""" _device: Switchbot async def async_turn_on(self, **kwargs: Any) -> None: """Turn device on.""" _LOGGER.debug("Turn Switchbot device on %s", self._address) self._last_run_success = bool(await self._device.turn_on()) if self._last_run_success: self._attr_is_on = True self.async_write_ha_state() async def async_turn_off(self, **kwargs: Any) -> None: """Turn device off.""" _LOGGER.debug("Turn Switchbot device off %s", self._address) self._last_run_success = bool(await self._device.turn_off()) if self._last_run_success: self._attr_is_on = False self.async_write_ha_state()