""" Entity for Zigbee Home Automation. For more details about this component, please refer to the documentation at https://home-assistant.io/components/zha/ """ from asyncio import sleep import logging from random import uniform from homeassistant.components.zha.const import ( DATA_ZHA, DATA_ZHA_BRIDGE_ID, DOMAIN) from homeassistant.components.zha.helpers import configure_reporting from homeassistant.core import callback from homeassistant.helpers import entity from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) class ZhaEntity(entity.Entity): """A base class for ZHA entities.""" _domain = None # Must be overridden by subclasses def __init__(self, endpoint, in_clusters, out_clusters, manufacturer, model, application_listener, unique_id, **kwargs): """Init ZHA entity.""" self._device_state_attributes = {} ieee = endpoint.device.ieee ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]]) if manufacturer and model is not None: self.entity_id = "{}.{}_{}_{}_{}{}".format( self._domain, slugify(manufacturer), slugify(model), ieeetail, endpoint.endpoint_id, kwargs.get('entity_suffix', ''), ) self._device_state_attributes['friendly_name'] = "{} {}".format( manufacturer, model, ) else: self.entity_id = "{}.zha_{}_{}{}".format( self._domain, ieeetail, endpoint.endpoint_id, kwargs.get('entity_suffix', ''), ) self._endpoint = endpoint self._in_clusters = in_clusters self._out_clusters = out_clusters self._state = None self._unique_id = unique_id # Normally the entity itself is the listener. Sub-classes may set this # to a dict of cluster ID -> listener to receive messages for specific # clusters separately self._in_listeners = {} self._out_listeners = {} self._initialized = False self.manufacturer_code = None application_listener.register_entity(ieee, self) async def async_added_to_hass(self): """Handle entity addition to hass. It is now safe to update the entity state """ for cluster_id, cluster in self._in_clusters.items(): cluster.add_listener(self._in_listeners.get(cluster_id, self)) for cluster_id, cluster in self._out_clusters.items(): cluster.add_listener(self._out_listeners.get(cluster_id, self)) self._initialized = True async def async_configure(self): """Set cluster binding and attribute reporting.""" for cluster_key, attrs in self.zcl_reporting_config.items(): cluster = self._get_cluster_from_report_config(cluster_key) if cluster is None: continue manufacturer = None if cluster.cluster_id >= 0xfc00 and self.manufacturer_code: manufacturer = self.manufacturer_code skip_bind = False # bind cluster only for the 1st configured attr for attr, details in attrs.items(): min_report_interval, max_report_interval, change = details await configure_reporting( self.entity_id, cluster, attr, min_report=min_report_interval, max_report=max_report_interval, reportable_change=change, skip_bind=skip_bind, manufacturer=manufacturer ) skip_bind = True await sleep(uniform(0.1, 0.5)) _LOGGER.debug("%s: finished configuration", self.entity_id) def _get_cluster_from_report_config(self, cluster_key): """Parse an entry from zcl_reporting_config dict.""" from zigpy.zcl import Cluster as Zcl_Cluster cluster = None if isinstance(cluster_key, Zcl_Cluster): cluster = cluster_key elif isinstance(cluster_key, str): cluster = getattr(self._endpoint, cluster_key, None) elif isinstance(cluster_key, int): if cluster_key in self._in_clusters: cluster = self._in_clusters[cluster_key] elif cluster_key in self._out_clusters: cluster = self._out_clusters[cluster_key] elif issubclass(cluster_key, Zcl_Cluster): cluster_id = cluster_key.cluster_id if cluster_id in self._in_clusters: cluster = self._in_clusters[cluster_id] elif cluster_id in self._out_clusters: cluster = self._out_clusters[cluster_id] return cluster @property def zcl_reporting_config(self): """Return a dict of ZCL attribute reporting configuration. { Cluster_Class: { attr_id: (min_report_interval, max_report_interval, change), attr_name: (min_rep_interval, max_rep_interval, change) } Cluster_Instance: { attr_id: (min_report_interval, max_report_interval, change), attr_name: (min_rep_interval, max_rep_interval, change) } cluster_id: { attr_id: (min_report_interval, max_report_interval, change), attr_name: (min_rep_interval, max_rep_interval, change) } 'cluster_name': { attr_id: (min_report_interval, max_report_interval, change), attr_name: (min_rep_interval, max_rep_interval, change) } } """ return dict() @property def unique_id(self) -> str: """Return a unique ID.""" return self._unique_id @property def device_state_attributes(self): """Return device specific state attributes.""" return self._device_state_attributes @callback def attribute_updated(self, attribute, value): """Handle an attribute updated on this cluster.""" pass @callback def zdo_command(self, tsn, command_id, args): """Handle a ZDO command received on this cluster.""" pass @property def device_info(self): """Return a device description for device registry.""" ieee = str(self._endpoint.device.ieee) return { 'connections': {(CONNECTION_ZIGBEE, ieee)}, 'identifiers': {(DOMAIN, ieee)}, 'manufacturer': self._endpoint.manufacturer, 'model': self._endpoint.model, 'name': self._device_state_attributes.get('friendly_name', ieee), 'via_hub': (DOMAIN, self.hass.data[DATA_ZHA][DATA_ZHA_BRIDGE_ID]), } @callback def zha_send_event(self, cluster, command, args): """Relay entity events to hass.""" pass # don't relay events from entities