def service(value): """Validate service.""" # Services use same format as entities so we can use same helper. if valid_entity_id(value): return value raise vol.Invalid('Service {} does not match format <domain>.<name>' .format(value))
def extract_entities(template, variables=None): """Extract all entities for state_changed listener from template string.""" if template is None or _RE_JINJA_DELIMITERS.search(template) is None: return [] if _RE_NONE_ENTITIES.search(template): return MATCH_ALL extraction = _RE_GET_ENTITIES.findall(template) extraction_final = [] for result in extraction: if result[0] == 'trigger.entity_id' and 'trigger' in variables and \ 'entity_id' in variables['trigger']: extraction_final.append(variables['trigger']['entity_id']) elif result[0]: extraction_final.append(result[0]) if variables and result[1] in variables and \ isinstance(variables[result[1]], str) and \ valid_entity_id(variables[result[1]]): extraction_final.append(variables[result[1]]) if extraction_final: return list(set(extraction_final)) return MATCH_ALL
def _async_update_entity(self, entity_id, *, name=_UNDEF, config_entry_id=_UNDEF, new_entity_id=_UNDEF, device_id=_UNDEF): """Private facing update properties method.""" old = self.entities[entity_id] changes = {} if name is not _UNDEF and name != old.name: changes['name'] = name if (config_entry_id is not _UNDEF and config_entry_id != old.config_entry_id): changes['config_entry_id'] = config_entry_id if (device_id is not _UNDEF and device_id != old.device_id): changes['device_id'] = device_id if new_entity_id is not _UNDEF and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError('Entity is already registered') if not valid_entity_id(new_entity_id): raise ValueError('Invalid entity ID') if (split_entity_id(new_entity_id)[0] != split_entity_id(entity_id)[0]): raise ValueError('New entity ID should be same domain') self.entities.pop(entity_id) entity_id = changes['entity_id'] = new_entity_id if not changes: return old new = self.entities[entity_id] = attr.evolve(old, **changes) to_remove = [] for listener_ref in new.update_listeners: listener = listener_ref() if listener is None: to_remove.append(listener) else: try: listener.async_registry_updated(old, new) except Exception: # pylint: disable=broad-except _LOGGER.exception('Error calling update listener') for ref in to_remove: new.update_listeners.remove(ref) self.async_schedule_save() return new
async def test_loading_invalid_entity_id(hass, hass_storage): """Test we autofix invalid entity IDs.""" hass_storage[entity_registry.STORAGE_KEY] = { 'version': entity_registry.STORAGE_VERSION, 'data': { 'entities': [ { 'entity_id': 'test.invalid__middle', 'platform': 'super_platform', 'unique_id': 'id-invalid-middle', 'name': 'registry override', }, { 'entity_id': 'test.invalid_end_', 'platform': 'super_platform', 'unique_id': 'id-invalid-end', }, { 'entity_id': 'test._invalid_start', 'platform': 'super_platform', 'unique_id': 'id-invalid-start', } ] } } registry = await entity_registry.async_get_registry(hass) entity_invalid_middle = registry.async_get_or_create( 'test', 'super_platform', 'id-invalid-middle') assert valid_entity_id(entity_invalid_middle.entity_id) entity_invalid_end = registry.async_get_or_create( 'test', 'super_platform', 'id-invalid-end') assert valid_entity_id(entity_invalid_end.entity_id) entity_invalid_start = registry.async_get_or_create( 'test', 'super_platform', 'id-invalid-start') assert valid_entity_id(entity_invalid_start.entity_id)
def _valid_customize(value): """Config validator for customize.""" if not isinstance(value, dict): raise vol.Invalid('Expected dictionary') for key, val in value.items(): if not valid_entity_id(key): raise vol.Invalid('Invalid entity ID: {}'.format(key)) if not isinstance(val, dict): raise vol.Invalid('Value of {} is not a dictionary'.format(key)) return value
def _async_add_entity(self, entity, update_before_add, component_entities): """Helper method to add an entity to the platform.""" if entity is None: raise ValueError('Entity cannot be None') # Do nothing if entity has already been added based on unique id. if entity in self.component.entities: return entity.hass = self.component.hass entity.platform = self entity.parallel_updates = self.parallel_updates # Update properties before we generate the entity_id if update_before_add: try: yield from entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.component.logger.exception( "%s: Error on device update!", self.platform) return # Write entity_id to entity if getattr(entity, 'entity_id', None) is None: object_id = entity.name or DEVICE_DEFAULT_NAME if self.entity_namespace is not None: object_id = '{} {}'.format(self.entity_namespace, object_id) entity.entity_id = async_generate_entity_id( self.component.entity_id_format, object_id, component_entities) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) elif entity.entity_id in component_entities: raise HomeAssistantError( 'Entity id already exists: {}'.format(entity.entity_id)) self.entities[entity.entity_id] = entity component_entities.add(entity.entity_id) if hasattr(entity, 'async_added_to_hass'): yield from entity.async_added_to_hass() yield from entity.async_update_ha_state()
def async_add_entity(self, entity, platform=None, update_before_add=False): """Add entity to component. This method must be run in the event loop. """ if entity is None or entity in self.entities.values(): return False entity.hass = self.hass # Update properties before we generate the entity_id if update_before_add: try: yield from entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception("Error on device update!") return False # Write entity_id to entity if getattr(entity, 'entity_id', None) is None: object_id = entity.name or DEVICE_DEFAULT_NAME if platform is not None and platform.entity_namespace is not None: object_id = '{} {}'.format(platform.entity_namespace, object_id) entity.entity_id = async_generate_entity_id( self.entity_id_format, object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if entity.entity_id in self.entities: raise HomeAssistantError( 'Entity id already exists: {}'.format(entity.entity_id)) elif not valid_entity_id(entity.entity_id): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) self.entities[entity.entity_id] = entity if hasattr(entity, 'async_added_to_hass'): yield from entity.async_added_to_hass() yield from entity.async_update_ha_state() return True
def entity_id(value: Any) -> str: """Validate Entity ID.""" value = string(value).lower() if valid_entity_id(value): return value if re.match(OLD_ENTITY_ID_VALIDATION, value): # To ease the breaking change, we allow old slugs for now # Remove after 0.94 or 1.0 fixed = '.'.join(util_slugify(part) for part in value.split('.', 1)) INVALID_ENTITY_IDS_FOUND[value] = fixed logging.getLogger(__name__).warning( "Found invalid entity_id %s, please update with %s. This " "will become a breaking change.", value, fixed ) return value raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value))
def async_add_entity(self, entity, platform=None, update_before_add=False): """Add entity to component. This method must be run in the event loop. """ if entity is None or entity in self.entities.values(): return False entity.hass = self.hass # update/init entity data if update_before_add: if hasattr(entity, 'async_update'): yield from entity.async_update() else: yield from self.hass.async_add_job(entity.update) if getattr(entity, 'entity_id', None) is None: object_id = entity.name or DEVICE_DEFAULT_NAME if platform is not None and platform.entity_namespace is not None: object_id = '{} {}'.format(platform.entity_namespace, object_id) entity.entity_id = async_generate_entity_id( self.entity_id_format, object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if entity.entity_id in self.entities: raise HomeAssistantError( 'Entity id already exists: {}'.format(entity.entity_id)) elif not valid_entity_id(entity.entity_id): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) self.entities[entity.entity_id] = entity if hasattr(entity, 'async_added_to_hass'): yield from entity.async_added_to_hass() yield from entity.async_update_ha_state() return True
def sensor_bypass(self, data=None): _LOGGER.info("Custom visonic alarm sensor bypass " + str(type(data))) #if type(data) is str: # _LOGGER.info(" Sensor_bypass = String " + str(data) ) if type(data) is dict or str(type(data)) == "<class 'mappingproxy'>": #_LOGGER.info(" Sensor_bypass = "******" A entity id = " + eid) if not eid.startswith('binary_sensor.'): eid = 'binary_sensor.' + eid #_LOGGER.info(" B entity id = " + eid) if valid_entity_id(eid): #_LOGGER.info(" C entity id = " + eid) mystate = self.hass.states.get(eid) if mystate is not None: #_LOGGER.info(" alarm mystate5 = " + str(mystate)) #_LOGGER.info(" alarm mystate6 = " + str(mystate.as_dict())) devid = mystate.attributes['visonic device'] code = '' if ATTR_CODE in data: code = data[ATTR_CODE] bypass = False if ATTR_BYPASS in data: bypass = data[ATTR_BYPASS] if devid >= 1 and devid <= 64: if bypass: _LOGGER.info( "Attempt to bypass sensor device id = " + str(devid)) else: _LOGGER.info( "Attempt to restore (arm) sensor device id = " + str(devid)) self.client.sendBypass(devid, bypass, self.decode_code(code)) else: _LOGGER.warning( "Attempt to bypass sensor with incorrect parameters device id = " + str(devid))
def alarm_arm_custom_bypass(self, data=None): if self.queue is not None: _LOGGER.info("alarm bypass = "******"alarm mystate1 = " + str(mystate)) _LOGGER.info("alarm mystate2 = " + str(mystate.as_dict())) #mystate = self.hass.states.get('binary_sensor.visonic_Z02') #if mystate is not None: # _LOGGER.info("alarm mystate3 = " + str(mystate)) # _LOGGER.info("alarm mystate4 = " + str(mystate.as_dict())) if valid_entity_id('binary_sensor.visonic_z02'): _LOGGER.info("its valid") else: _LOGGER.info("its not valid") mystate = self.hass.states.get('binary_sensor.visonic_z02') if mystate is not None: _LOGGER.info("alarm mystate5 = " + str(mystate)) _LOGGER.info("alarm mystate6 = " + str(mystate.as_dict())) devid = mystate.attributes['visonic device'] if 'command' in data: comm = data['command'] _LOGGER.info("alarm mystate7 = " + str(devid) + " command " + str(comm)) self.queue.put_nowait([ "bypass", devid, comm, self.decode_code(data) ]) else: _LOGGER.info("alarm mystate8 = " + str(devid)) self.queue.put_nowait([ "bypass", devid, False, self.decode_code(data) ])
async def async_load(self) -> None: """Load the entity registry.""" async_setup_entity_restore(self.hass, self) data = await self.hass.helpers.storage.async_migrator( self.hass.config.path(PATH_REGISTRY), self._store, old_conf_load_func=load_yaml, old_conf_migrate_func=_async_migrate, ) entities: dict[str, RegistryEntry] = OrderedDict() if data is not None: for entity in data["entities"]: # Some old installations can have some bad entities. # Filter them out as they cause errors down the line. # Can be removed in Jan 2021 if not valid_entity_id(entity["entity_id"]): continue entities[entity["entity_id"]] = RegistryEntry( entity_id=entity["entity_id"], config_entry_id=entity.get("config_entry_id"), device_id=entity.get("device_id"), area_id=entity.get("area_id"), unique_id=entity["unique_id"], platform=entity["platform"], name=entity.get("name"), icon=entity.get("icon"), disabled_by=entity.get("disabled_by"), capabilities=entity.get("capabilities") or {}, supported_features=entity.get("supported_features", 0), device_class=entity.get("device_class"), unit_of_measurement=entity.get("unit_of_measurement"), original_name=entity.get("original_name"), original_icon=entity.get("original_icon"), entity_category=entity.get("entity_category"), ) self.entities = entities self._rebuild_index()
def _async_validate_cost_stat( hass: HomeAssistant, metadata: dict[str, tuple[int, recorder.models.StatisticMetaData]], stat_id: str, result: list[ValidationIssue], ) -> None: """Validate that the cost stat is correct.""" if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue("recorder_untracked", stat_id)) if (state := hass.states.get(stat_id)) is None: result.append(ValidationIssue("entity_not_defined", stat_id)) return
def _async_validate_cost_stat(hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost stat is correct.""" has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue( "recorder_untracked", stat_id, )) state = hass.states.get(stat_id) if state is None: result.append(ValidationIssue( "entity_not_defined", stat_id, )) return state_class = state.attributes.get("state_class") supported_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in supported_state_classes: result.append( ValidationIssue("entity_unexpected_state_class", stat_id, state_class)) if (state_class == sensor.STATE_CLASS_MEASUREMENT and sensor.ATTR_LAST_RESET not in state.attributes): result.append( ValidationIssue("entity_state_class_measurement_no_last_reset", stat_id))
def extract_entities( hass: HomeAssistantType, template: Optional[str], variables: Optional[Dict[str, Any]] = None, ) -> Union[str, List[str]]: """Extract all entities for state_changed listener from template string.""" if template is None or _RE_JINJA_DELIMITERS.search(template) is None: return [] if _RE_NONE_ENTITIES.search(template): return MATCH_ALL extraction_final = [] for result in _RE_GET_ENTITIES.finditer(template): if ( result.group("entity_id") == "trigger.entity_id" and variables and "trigger" in variables and "entity_id" in variables["trigger"] ): extraction_final.append(variables["trigger"]["entity_id"]) elif result.group("entity_id"): if result.group("func") == "expand": for entity in expand(hass, result.group("entity_id")): extraction_final.append(entity.entity_id) extraction_final.append(result.group("entity_id")) if ( variables and result.group("variable") in variables and isinstance(variables[result.group("variable")], str) and valid_entity_id(variables[result.group("variable")]) ): extraction_final.append(variables[result.group("variable")]) if extraction_final: return list(set(extraction_final)) return MATCH_ALL
def handle_set_max_current(call): """Handle the service call to set the absolute max current.""" maxCurrentInput = call.data.get( SET_MAX_CURRENT_ATTR, hass.data[DOMAIN][ABSOLUTE_MAX_CURRENT]) if isinstance(maxCurrentInput, str): if maxCurrentInput.isnumeric(): maxCurrent = int(maxCurrentInput) elif valid_entity_id(maxCurrentInput): maxCurrent = int(hass.states.get(maxCurrentInput).state) else: _LOGGER.error("No valid value for '%s': %s", SET_MAX_CURRENT_ATTR, maxCurrent) return else: maxCurrent = maxCurrentInput if maxCurrent < 6: maxCurrent = 6 if maxCurrent > 32: maxCurrent = 32 hass.data[DOMAIN] = goeCharger.setMaxCurrent(maxCurrent) hass.data[DOMAIN]["age"] = utcnow().timestamp()
async def _async_validate_usage_stat( hass: HomeAssistant, stat_id: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" metadata = await hass.async_add_executor_job( functools.partial( recorder.statistics.get_metadata, hass, statistic_ids=(stat_id, ), )) if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity_source = valid_entity_id(stat_id) if not has_entity_source: return entity_id = stat_id if not recorder.is_entity_recorded(hass, entity_id): result.append(ValidationIssue( "recorder_untracked", entity_id, )) return if (state := hass.states.get(entity_id)) is None: result.append(ValidationIssue( "entity_not_defined", entity_id, )) return
def async_add_entity(self, entity, platform=None, update_before_add=False): """Add entity to component. This method must be run in the event loop. """ if entity is None or entity in self.entities.values(): return False entity.hass = self.hass # update/init entity data if update_before_add: if hasattr(entity, 'async_update'): yield from entity.async_update() else: yield from self.hass.loop.run_in_executor(None, entity.update) if getattr(entity, 'entity_id', None) is None: object_id = entity.name or DEVICE_DEFAULT_NAME if platform is not None and platform.entity_namespace is not None: object_id = '{} {}'.format(platform.entity_namespace, object_id) entity.entity_id = async_generate_entity_id( self.entity_id_format, object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if entity.entity_id in self.entities: raise HomeAssistantError('Entity id already exists: {}'.format( entity.entity_id)) elif not valid_entity_id(entity.entity_id): raise HomeAssistantError('Invalid entity id: {}'.format( entity.entity_id)) self.entities[entity.entity_id] = entity yield from entity.async_update_ha_state() return True
def extract_entities(template, variables=None): """Extract all entities for state_changed listener from template string.""" if template is None or _RE_NONE_ENTITIES.search(template): return MATCH_ALL extraction = _RE_GET_ENTITIES.findall(template) extraction_final = [] for result in extraction: if result[0] == 'trigger.entity_id' and 'trigger' in variables and \ 'entity_id' in variables['trigger']: extraction_final.append(variables['trigger']['entity_id']) elif result[0]: extraction_final.append(result[0]) if variables and result[1] in variables and \ isinstance(variables[result[1]], str) and \ valid_entity_id(variables[result[1]]): extraction_final.append(variables[result[1]]) if extraction_final: return list(set(extraction_final)) return MATCH_ALL
def _async_validate_usage_stat( hass: HomeAssistant, metadata: dict[str, tuple[int, recorder.models.StatisticMetaData]], stat_id: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity_source = valid_entity_id(stat_id) if not has_entity_source: return entity_id = stat_id if not recorder.is_entity_recorded(hass, entity_id): result.append( ValidationIssue( "recorder_untracked", entity_id, ) ) return if (state := hass.states.get(entity_id)) is None: result.append( ValidationIssue( "entity_not_defined", entity_id, ) ) return
async def _async_validate_cost_stat(hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]) -> None: """Validate that the cost stat is correct.""" metadata = await hass.async_add_executor_job( functools.partial( recorder.statistics.get_metadata, hass, statistic_ids=(stat_id, ), )) if stat_id not in metadata: result.append(ValidationIssue("statistics_not_defined", stat_id)) has_entity = valid_entity_id(stat_id) if not has_entity: return if not recorder.is_entity_recorded(hass, stat_id): result.append(ValidationIssue("recorder_untracked", stat_id)) if (state := hass.states.get(stat_id)) is None: result.append(ValidationIssue("entity_not_defined", stat_id)) return
def _get_state_if_valid(hass: HomeAssistant, entity_id: str) -> TemplateState | None: state = hass.states.get(entity_id) if state is None and not valid_entity_id(entity_id): raise TemplateError(f"Invalid entity ID '{entity_id}'") # type: ignore return _get_template_state_from_state(hass, entity_id, state)
async def valid_entity_id(hass): """Run valid entity ID a million times.""" start = timer() for _ in range(10**6): core.valid_entity_id("light.kitchen") return timer() - start
def _async_update_entity( self, entity_id, *, name=_UNDEF, config_entry_id=_UNDEF, new_entity_id=_UNDEF, device_id=_UNDEF, new_unique_id=_UNDEF, ): """Private facing update properties method.""" old = self.entities[entity_id] changes = {} if name is not _UNDEF and name != old.name: changes["name"] = name if config_entry_id is not _UNDEF and config_entry_id != old.config_entry_id: changes["config_entry_id"] = config_entry_id if device_id is not _UNDEF and device_id != old.device_id: changes["device_id"] = device_id if new_entity_id is not _UNDEF and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id( entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = changes["entity_id"] = new_entity_id if new_unique_id is not _UNDEF: conflict = next( (entity for entity in self.entities.values() if entity.unique_id == new_unique_id and entity.domain == old.domain and entity.platform == old.platform), None, ) if conflict: raise ValueError( "Unique id '{}' is already in use by '{}'".format( new_unique_id, conflict.entity_id)) changes["unique_id"] = new_unique_id if not changes: return old new = self.entities[entity_id] = attr.evolve(old, **changes) self.async_schedule_save() data = {"action": "update", "entity_id": entity_id} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new
async def _async_add_entity(self, entity, update_before_add, component_entities, registry): """Helper method to add an entity to the platform.""" if entity is None: raise ValueError('Entity cannot be None') entity.hass = self.hass entity.platform = self entity.parallel_updates = self.parallel_updates # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception( "%s: Error on device update!", self.platform_name) return suggested_object_id = None # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name if self.entity_namespace is not None: suggested_object_id = '{} {}'.format( self.entity_namespace, suggested_object_id) entry = registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id) if entry.disabled: self.logger.info( "Not adding entity %s because it's disabled", entry.name or entity.name or '"{} {}"'.format(self.platform_name, entity.unique_id)) return entity.entity_id = entry.entity_id entity.registry_name = entry.name entry.add_update_listener(entity) # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif (entity.entity_id is not None and registry.async_is_registered(entity.entity_id)): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] entity.entity_id = None # Generate entity ID if entity.entity_id is None: suggested_object_id = \ suggested_object_id or entity.name or DEVICE_DEFAULT_NAME if self.entity_namespace is not None: suggested_object_id = '{} {}'.format(self.entity_namespace, suggested_object_id) entity.entity_id = registry.async_generate_entity_id( self.domain, suggested_object_id) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) elif entity.entity_id in component_entities: msg = 'Entity id already exists: {}'.format(entity.entity_id) if entity.unique_id is not None: msg += '. Platform {} does not generate unique IDs'.format( self.platform_name) raise HomeAssistantError( msg) self.entities[entity.entity_id] = entity component_entities.add(entity.entity_id) if hasattr(entity, 'async_added_to_hass'): await entity.async_added_to_hass() await entity.async_update_ha_state()
def entity_id(value: Any) -> str: """Validate Entity ID.""" value = string(value).lower() if valid_entity_id(value): return value raise vol.Invalid('Entity ID {} is an invalid entity id'.format(value))
async def service_sensor_bypass(self, call): """Service call to bypass individual sensors.""" # This function concerns itself with the service call and decoding the parameters for bypassing the sensor _LOGGER.debug("Custom visonic alarm sensor bypass %s", str(type(call.data))) if not self.isPanelConnected(): raise HomeAssistantError( f"Visonic Integration {self._myname} not connected to panel.") if type(call.data) is dict or str(type( call.data)) == "<class 'mappingproxy'>": # _LOGGER.debug(" Sensor_bypass = %s", str(type(call.data))) # self.dump_dict(call.data) if ATTR_ENTITY_ID in call.data: eid = str(call.data[ATTR_ENTITY_ID]) if not eid.startswith("binary_sensor."): eid = "binary_sensor." + eid if valid_entity_id(eid): if call.context.user_id: entity_id = call.data[ ATTR_ENTITY_ID] # just in case it's not a string for raising the exceptions user = await self.hass.auth.async_get_user( call.context.user_id) if user is None: raise UnknownUser( context=call.context, entity_id=entity_id, permission=POLICY_CONTROL, ) if not user.permissions.check_entity( entity_id, POLICY_CONTROL): raise Unauthorized( context=call.context, entity_id=entity_id, permission=POLICY_CONTROL, ) bypass = False if ATTR_BYPASS in call.data: bypass = call.data[ATTR_BYPASS] code = "" if ATTR_CODE in call.data: code = call.data[ATTR_CODE] # If the code is defined then it must be a 4 digit string if len(code) > 0 and not re.search(PIN_REGEX, code): code = "0000" self.sensor_bypass(eid, bypass, code) else: self._client.createWarningMessage( AvailableNotifications.BYPASS_PROBLEM, f"Attempt to bypass sensor, invalid entity {eid}") else: self._client.createWarningMessage( AvailableNotifications.BYPASS_PROBLEM, f"Attempt to bypass sensor but entity not defined") else: self._client.createWarningMessage( AvailableNotifications.BYPASS_PROBLEM, f"Attempt to bypass sensor but entity not defined")
async def _async_add_entity(self, entity, update_before_add, entity_registry, device_registry): """Add an entity to the platform.""" if entity is None: raise ValueError('Entity cannot be None') entity.hass = self.hass entity.platform = self # Async entity # PARALLEL_UPDATE == None: entity.parallel_updates = None # PARALLEL_UPDATE == 0: entity.parallel_updates = None # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) # Sync entity # PARALLEL_UPDATE == None: entity.parallel_updates = Semaphore(1) # PARALLEL_UPDATE == 0: entity.parallel_updates = None # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) if hasattr(entity, 'async_update') and not self.parallel_updates: entity.parallel_updates = None elif (not hasattr(entity, 'async_update') and self.parallel_updates == 0): entity.parallel_updates = None else: entity.parallel_updates = self._get_parallel_updates_semaphore() # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception( "%s: Error on device update!", self.platform_name) return suggested_object_id = None # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name if self.entity_namespace is not None: suggested_object_id = '{} {}'.format( self.entity_namespace, suggested_object_id) if self.config_entry is not None: config_entry_id = self.config_entry.entry_id else: config_entry_id = None device_info = entity.device_info device_id = None if config_entry_id is not None and device_info is not None: processed_dev_info = { 'config_entry_id': config_entry_id } for key in ( 'connections', 'identifiers', 'manufacturer', 'model', 'name', 'sw_version', 'via_hub', ): if key in device_info: processed_dev_info[key] = device_info[key] device = device_registry.async_get_or_create( **processed_dev_info) if device: device_id = device.id entry = entity_registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id, config_entry_id=config_entry_id, device_id=device_id, known_object_ids=self.entities.keys()) if entry.disabled: self.logger.info( "Not adding entity %s because it's disabled", entry.name or entity.name or '"{} {}"'.format(self.platform_name, entity.unique_id)) return entity.entity_id = entry.entity_id entity.registry_name = entry.name entity.async_on_remove(entry.add_update_listener(entity)) # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif (entity.entity_id is not None and entity_registry.async_is_registered(entity.entity_id)): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] entity.entity_id = None # Generate entity ID if entity.entity_id is None: suggested_object_id = \ suggested_object_id or entity.name or DEVICE_DEFAULT_NAME if self.entity_namespace is not None: suggested_object_id = '{} {}'.format(self.entity_namespace, suggested_object_id) entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError( 'Invalid entity id: {}'.format(entity.entity_id)) if (entity.entity_id in self.entities or entity.entity_id in self.hass.states.async_entity_ids( self.domain)): msg = 'Entity id already exists: {}'.format(entity.entity_id) if entity.unique_id is not None: msg += '. Platform {} does not generate unique IDs'.format( self.platform_name) raise HomeAssistantError(msg) entity_id = entity.entity_id self.entities[entity_id] = entity entity.async_on_remove(lambda: self.entities.pop(entity_id)) await entity.async_added_to_hass() await entity.async_update_ha_state()
def _async_update_entity( self, entity_id, *, name=_UNDEF, icon=_UNDEF, config_entry_id=_UNDEF, new_entity_id=_UNDEF, device_id=_UNDEF, new_unique_id=_UNDEF, disabled_by=_UNDEF, capabilities=_UNDEF, supported_features=_UNDEF, device_class=_UNDEF, unit_of_measurement=_UNDEF, original_name=_UNDEF, original_icon=_UNDEF, ): """Private facing update properties method.""" old = self.entities[entity_id] changes = {} for attr_name, value in ( ("name", name), ("icon", icon), ("config_entry_id", config_entry_id), ("device_id", device_id), ("disabled_by", disabled_by), ("capabilities", capabilities), ("supported_features", supported_features), ("device_class", device_class), ("unit_of_measurement", unit_of_measurement), ("original_name", original_name), ("original_icon", original_icon), ): if value is not _UNDEF and value != getattr(old, attr_name): changes[attr_name] = value if new_entity_id is not _UNDEF and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id(entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = changes["entity_id"] = new_entity_id if new_unique_id is not _UNDEF: conflict = next( ( entity for entity in self.entities.values() if entity.unique_id == new_unique_id and entity.domain == old.domain and entity.platform == old.platform ), None, ) if conflict: raise ValueError( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict.entity_id}'" ) changes["unique_id"] = new_unique_id if not changes: return old new = self.entities[entity_id] = attr.evolve(old, **changes) self.async_schedule_save() data = {"action": "update", "entity_id": entity_id, "changes": list(changes)} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new
def async_update_entity( self, entity_id: str, *, area_id: str | None | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, device_id: str | None | UndefinedType = UNDEFINED, disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, # Type str (ENTITY_CATEG*) is deprecated as of 2021.12, use EntityCategory entity_category: EntityCategory | str | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, original_device_class: str | None | UndefinedType = UNDEFINED, original_icon: str | None | UndefinedType = UNDEFINED, original_name: str | None | UndefinedType = UNDEFINED, supported_features: int | UndefinedType = UNDEFINED, unit_of_measurement: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] new_values: dict[str, Any] = {} # Dict with new key/value pairs old_values: dict[str, Any] = {} # Dict with old key/value pairs if isinstance(disabled_by, str) and not isinstance( disabled_by, RegistryEntryDisabler ): report( # type: ignore[unreachable] "uses str for entity registry disabled_by. This is deprecated and will " "stop working in Home Assistant 2022.3, it should be updated to use " "RegistryEntryDisabler instead", error_if_core=False, ) disabled_by = RegistryEntryDisabler(disabled_by) for attr_name, value in ( ("area_id", area_id), ("capabilities", capabilities), ("config_entry_id", config_entry_id), ("device_class", device_class), ("device_id", device_id), ("disabled_by", disabled_by), ("entity_category", entity_category), ("icon", icon), ("name", name), ("original_device_class", original_device_class), ("original_icon", original_icon), ("original_name", original_name), ("supported_features", supported_features), ("unit_of_measurement", unit_of_measurement), ): if value is not UNDEFINED and value != getattr(old, attr_name): new_values[attr_name] = value old_values[attr_name] = getattr(old, attr_name) if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity with this ID is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id(entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = new_values["entity_id"] = new_entity_id old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( old.domain, old.platform, new_unique_id ) if conflict_entity_id: raise ValueError( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) new_values["unique_id"] = new_unique_id old_values["unique_id"] = old.unique_id if not new_values: return old new = self.entities[entity_id] = attr.evolve(old, **new_values) self.async_schedule_save() data: dict[str, str | dict[str, Any]] = { "action": "update", "entity_id": entity_id, "changes": old_values, } if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new
def _async_update_entity( self, entity_id: str, *, name: str | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, device_id: str | None | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, disabled_by: str | None | UndefinedType = UNDEFINED, capabilities: dict[str, Any] | None | UndefinedType = UNDEFINED, supported_features: int | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, unit_of_measurement: str | None | UndefinedType = UNDEFINED, original_name: str | None | UndefinedType = UNDEFINED, original_icon: str | None | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] new_values = {} # Dict with new key/value pairs old_values = {} # Dict with old key/value pairs for attr_name, value in ( ("name", name), ("icon", icon), ("config_entry_id", config_entry_id), ("device_id", device_id), ("area_id", area_id), ("disabled_by", disabled_by), ("capabilities", capabilities), ("supported_features", supported_features), ("device_class", device_class), ("unit_of_measurement", unit_of_measurement), ("original_name", original_name), ("original_icon", original_icon), ): if value is not UNDEFINED and value != getattr(old, attr_name): new_values[attr_name] = value old_values[attr_name] = getattr(old, attr_name) if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity with this ID is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id(entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = new_values["entity_id"] = new_entity_id old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( old.domain, old.platform, new_unique_id ) if conflict_entity_id: raise ValueError( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) new_values["unique_id"] = new_unique_id old_values["unique_id"] = old.unique_id if not new_values: return old self._remove_index(old) new = attr.evolve(old, **new_values) self._register_entry(new) self.async_schedule_save() data = {"action": "update", "entity_id": entity_id, "changes": old_values} if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new
def __getattr__(self, name): """Return the states.""" entity_id = "{}.{}".format(self._domain, name) if not valid_entity_id(entity_id): raise TemplateError("Invalid entity ID '{}'".format(entity_id)) return _get_state(self._hass, entity_id)
async def _async_add_entity( # noqa: C901 self, entity: Entity, update_before_add: bool, entity_registry: EntityRegistry, device_registry: DeviceRegistry, ) -> None: """Add an entity to the platform.""" if entity is None: raise ValueError("Entity cannot be None") entity.add_to_platform_start( self.hass, self, self._get_parallel_updates_semaphore(hasattr(entity, "update")), ) # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception("%s: Error on device update!", self.platform_name) entity.add_to_platform_abort() return requested_entity_id = None suggested_object_id: str | None = None generate_new_entity_id = False # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: requested_entity_id = entity.entity_id suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name # type: ignore[unreachable] if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: config_entry_id: str | None = self.config_entry.entry_id else: config_entry_id = None device_info = entity.device_info device_id = None device = None if config_entry_id is not None and device_info is not None: processed_dev_info: dict[str, str | None] = { "config_entry_id": config_entry_id } for key in ( "connections", "default_manufacturer", "default_model", "default_name", "entry_type", "identifiers", "manufacturer", "model", "name", "suggested_area", "sw_version", "hw_version", "via_device", ): if key in device_info: processed_dev_info[key] = device_info[ key] # type: ignore[literal-required] if "configuration_url" in device_info: if device_info["configuration_url"] is None: processed_dev_info["configuration_url"] = None else: configuration_url = str( device_info["configuration_url"]) if urlparse(configuration_url).scheme in [ "http", "https", "homeassistant", ]: processed_dev_info[ "configuration_url"] = configuration_url else: _LOGGER.warning( "Ignoring invalid device configuration_url '%s'", configuration_url, ) try: device = device_registry.async_get_or_create( **processed_dev_info) # type: ignore[arg-type] device_id = device.id except RequiredParameterMissing: pass disabled_by: RegistryEntryDisabler | None = None if not entity.entity_registry_enabled_default: disabled_by = RegistryEntryDisabler.INTEGRATION hidden_by: RegistryEntryHider | None = None if not entity.entity_registry_visible_default: hidden_by = RegistryEntryHider.INTEGRATION entry = entity_registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, capabilities=entity.capability_attributes, config_entry=self.config_entry, device_id=device_id, disabled_by=disabled_by, entity_category=entity.entity_category, hidden_by=hidden_by, known_object_ids=self.entities.keys(), original_device_class=entity.device_class, original_icon=entity.icon, original_name=entity.name, suggested_object_id=suggested_object_id, supported_features=entity.supported_features, unit_of_measurement=entity.unit_of_measurement, ) if device and device.disabled and not entry.disabled: entry = entity_registry.async_update_entity( entry.entity_id, disabled_by=RegistryEntryDisabler.DEVICE) entity.registry_entry = entry entity.entity_id = entry.entity_id if entry.disabled: self.logger.debug( "Not adding entity %s because it's disabled", entry.name or entity.name or f'"{self.platform_name} {entity.unique_id}"', ) entity.add_to_platform_abort() return # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif entity.entity_id is not None and entity_registry.async_is_registered( entity.entity_id): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] generate_new_entity_id = True # Generate entity ID if entity.entity_id is None or generate_new_entity_id: suggested_object_id = (suggested_object_id or entity.name or DEVICE_DEFAULT_NAME) if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): entity.add_to_platform_abort() raise HomeAssistantError(f"Invalid entity ID: {entity.entity_id}") already_exists = entity.entity_id in self.entities restored = False if not already_exists and not self.hass.states.async_available( entity.entity_id): existing = self.hass.states.get(entity.entity_id) if existing is not None and ATTR_RESTORED in existing.attributes: restored = True else: already_exists = True if already_exists: if entity.unique_id is not None: msg = f"Platform {self.platform_name} does not generate unique IDs. " if requested_entity_id: msg += f"ID {entity.unique_id} is already used by {entity.entity_id} - ignoring {requested_entity_id}" else: msg += f"ID {entity.unique_id} already exists - ignoring {entity.entity_id}" else: msg = f"Entity id already exists - ignoring: {entity.entity_id}" self.logger.error(msg) entity.add_to_platform_abort() return entity_id = entity.entity_id self.entities[entity_id] = entity if not restored: # Reserve the state in the state machine # because as soon as we return control to the event # loop below, another entity could be added # with the same id before `entity.add_to_platform_finish()` # has a chance to finish. self.hass.states.async_reserve(entity.entity_id) def remove_entity_cb() -> None: """Remove entity from entities dict.""" self.entities.pop(entity_id) entity.async_on_remove(remove_entity_cb) await entity.add_to_platform_finish()
def _async_update_entity( self, entity_id: str, *, area_id: str | None | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED, device_id: str | None | UndefinedType = UNDEFINED, disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, entity_category: EntityCategory | None | UndefinedType = UNDEFINED, hidden_by: RegistryEntryHider | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, original_device_class: str | None | UndefinedType = UNDEFINED, original_icon: str | None | UndefinedType = UNDEFINED, original_name: str | None | UndefinedType = UNDEFINED, supported_features: int | UndefinedType = UNDEFINED, unit_of_measurement: str | None | UndefinedType = UNDEFINED, platform: str | None | UndefinedType = UNDEFINED, options: Mapping[str, Mapping[str, Any]] | UndefinedType = UNDEFINED, ) -> RegistryEntry: """Private facing update properties method.""" old = self.entities[entity_id] new_values: dict[str, Any] = {} # Dict with new key/value pairs old_values: dict[str, Any] = {} # Dict with old key/value pairs if ( disabled_by and disabled_by is not UNDEFINED and not isinstance(disabled_by, RegistryEntryDisabler) ): raise ValueError("disabled_by must be a RegistryEntryDisabler value") from .entity import EntityCategory # pylint: disable=import-outside-toplevel if ( entity_category and entity_category is not UNDEFINED and not isinstance(entity_category, EntityCategory) ): raise ValueError("entity_category must be a valid EntityCategory instance") for attr_name, value in ( ("area_id", area_id), ("capabilities", capabilities), ("config_entry_id", config_entry_id), ("device_class", device_class), ("device_id", device_id), ("disabled_by", disabled_by), ("entity_category", entity_category), ("hidden_by", hidden_by), ("icon", icon), ("name", name), ("original_device_class", original_device_class), ("original_icon", original_icon), ("original_name", original_name), ("supported_features", supported_features), ("unit_of_measurement", unit_of_measurement), ("platform", platform), ("options", options), ): if value is not UNDEFINED and value != getattr(old, attr_name): new_values[attr_name] = value old_values[attr_name] = getattr(old, attr_name) if new_entity_id is not UNDEFINED and new_entity_id != old.entity_id: if self.async_is_registered(new_entity_id): raise ValueError("Entity with this ID is already registered") if not valid_entity_id(new_entity_id): raise ValueError("Invalid entity ID") if split_entity_id(new_entity_id)[0] != split_entity_id(entity_id)[0]: raise ValueError("New entity ID should be same domain") self.entities.pop(entity_id) entity_id = new_values["entity_id"] = new_entity_id old_values["entity_id"] = old.entity_id if new_unique_id is not UNDEFINED: conflict_entity_id = self.async_get_entity_id( old.domain, old.platform, new_unique_id ) if conflict_entity_id: raise ValueError( f"Unique id '{new_unique_id}' is already in use by " f"'{conflict_entity_id}'" ) new_values["unique_id"] = new_unique_id old_values["unique_id"] = old.unique_id if not new_values: return old new = self.entities[entity_id] = attr.evolve(old, **new_values) self.async_schedule_save() data: dict[str, str | dict[str, Any]] = { "action": "update", "entity_id": entity_id, "changes": old_values, } if old.entity_id != entity_id: data["old_entity_id"] = old.entity_id self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data) return new
async def _async_add_entity(self, entity, update_before_add, entity_registry, device_registry): """Add an entity to the platform.""" if entity is None: raise ValueError("Entity cannot be None") entity.add_to_platform_start( self.hass, self, self._get_parallel_updates_semaphore( hasattr(entity, "async_update")), ) # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception("%s: Error on device update!", self.platform_name) entity.add_to_platform_abort() return requested_entity_id = None suggested_object_id: Optional[str] = None # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: requested_entity_id = entity.entity_id suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: config_entry_id: Optional[str] = self.config_entry.entry_id else: config_entry_id = None device_info = entity.device_info device_id = None if config_entry_id is not None and device_info is not None: processed_dev_info = {"config_entry_id": config_entry_id} for key in ( "connections", "identifiers", "manufacturer", "model", "name", "default_manufacturer", "default_model", "default_name", "sw_version", "entry_type", "via_device", ): if key in device_info: processed_dev_info[key] = device_info[key] device = device_registry.async_get_or_create( **processed_dev_info) if device: device_id = device.id disabled_by: Optional[str] = None if not entity.entity_registry_enabled_default: disabled_by = DISABLED_INTEGRATION entry = entity_registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id, config_entry=self.config_entry, device_id=device_id, known_object_ids=self.entities.keys(), disabled_by=disabled_by, capabilities=entity.capability_attributes, supported_features=entity.supported_features, device_class=entity.device_class, unit_of_measurement=entity.unit_of_measurement, original_name=entity.name, original_icon=entity.icon, ) entity.registry_entry = entry entity.entity_id = entry.entity_id if entry.disabled: self.logger.info( "Not adding entity %s because it's disabled", entry.name or entity.name or f'"{self.platform_name} {entity.unique_id}"', ) entity.add_to_platform_abort() return # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif entity.entity_id is not None and entity_registry.async_is_registered( entity.entity_id): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] entity.entity_id = None # Generate entity ID if entity.entity_id is None: suggested_object_id = (suggested_object_id or entity.name or DEVICE_DEFAULT_NAME) if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): entity.add_to_platform_abort() raise HomeAssistantError(f"Invalid entity id: {entity.entity_id}") already_exists = entity.entity_id in self.entities restored = False if not already_exists and not self.hass.states.async_available( entity.entity_id): existing = self.hass.states.get(entity.entity_id) if existing is not None and ATTR_RESTORED in existing.attributes: restored = True else: already_exists = True if already_exists: if entity.unique_id is not None: msg = f"Platform {self.platform_name} does not generate unique IDs. " if requested_entity_id: msg += f"ID {entity.unique_id} is already used by {entity.entity_id} - ignoring {requested_entity_id}" else: msg += f"ID {entity.unique_id} already exists - ignoring {entity.entity_id}" else: msg = f"Entity id already exists - ignoring: {entity.entity_id}" self.logger.error(msg) entity.add_to_platform_abort() return entity_id = entity.entity_id self.entities[entity_id] = entity if not restored: # Reserve the state in the state machine # because as soon as we return control to the event # loop below, another entity could be added # with the same id before `entity.add_to_platform_finish()` # has a chance to finish. self.hass.states.async_reserve(entity.entity_id) entity.async_on_remove(lambda: self.entities.pop(entity_id)) await entity.add_to_platform_finish()
def _async_validate_usage_stat( hass: HomeAssistant, stat_value: str, allowed_device_classes: Sequence[str], allowed_units: Mapping[str, Sequence[str]], unit_error: str, result: list[ValidationIssue], ) -> None: """Validate a statistic.""" has_entity_source = valid_entity_id(stat_value) if not has_entity_source: return if not recorder.is_entity_recorded(hass, stat_value): result.append(ValidationIssue( "recorder_untracked", stat_value, )) return state = hass.states.get(stat_value) if state is None: result.append(ValidationIssue( "entity_not_defined", stat_value, )) return if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN): result.append( ValidationIssue("entity_unavailable", stat_value, state.state)) return try: current_value: float | None = float(state.state) except ValueError: result.append( ValidationIssue("entity_state_non_numeric", stat_value, state.state)) return if current_value is not None and current_value < 0: result.append( ValidationIssue("entity_negative_state", stat_value, current_value)) device_class = state.attributes.get(ATTR_DEVICE_CLASS) if device_class not in allowed_device_classes: result.append( ValidationIssue( "entity_unexpected_device_class", stat_value, device_class, )) else: unit = state.attributes.get("unit_of_measurement") if device_class and unit not in allowed_units.get(device_class, []): result.append(ValidationIssue(unit_error, stat_value, unit)) state_class = state.attributes.get(sensor.ATTR_STATE_CLASS) allowed_state_classes = [ sensor.STATE_CLASS_MEASUREMENT, sensor.STATE_CLASS_TOTAL, sensor.STATE_CLASS_TOTAL_INCREASING, ] if state_class not in allowed_state_classes: result.append( ValidationIssue( "entity_unexpected_state_class", stat_value, state_class, )) if (state_class == sensor.STATE_CLASS_MEASUREMENT and sensor.ATTR_LAST_RESET not in state.attributes): result.append( ValidationIssue("entity_state_class_measurement_no_last_reset", stat_value))
def resolve_entity(entity_id_or_uuid: str) -> str | None: """Resolve an entity id or UUID to an entity id or None.""" if valid_entity_id(entity_id_or_uuid): return entity_id_or_uuid if (entry := registry.entities.get_entry(entity_id_or_uuid)) is None: raise vol.Invalid(f"Unknown entity registry entry {entity_id_or_uuid}")
def __getattr__(self, name): """Return the states.""" entity_id = f"{self._domain}.{name}" if not valid_entity_id(entity_id): raise TemplateError(f"Invalid entity ID '{entity_id}'") return _get_state(self._hass, entity_id)
async def _async_add_entity( self, entity, update_before_add, entity_registry, device_registry ): """Add an entity to the platform.""" if entity is None: raise ValueError("Entity cannot be None") entity.hass = self.hass entity.platform = self # Async entity # PARALLEL_UPDATES == None: entity.parallel_updates = None # PARALLEL_UPDATES == 0: entity.parallel_updates = None # PARALLEL_UPDATES > 0: entity.parallel_updates = Semaphore(p) # Sync entity # PARALLEL_UPDATES == None: entity.parallel_updates = Semaphore(1) # PARALLEL_UPDATES == 0: entity.parallel_updates = None # PARALLEL_UPDATES > 0: entity.parallel_updates = Semaphore(p) if hasattr(entity, "async_update") and not self.parallel_updates: entity.parallel_updates = None elif not hasattr(entity, "async_update") and self.parallel_updates == 0: entity.parallel_updates = None else: entity.parallel_updates = self._get_parallel_updates_semaphore() # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception("%s: Error on device update!", self.platform_name) return suggested_object_id = None # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" if self.config_entry is not None: config_entry_id = self.config_entry.entry_id else: config_entry_id = None device_info = entity.device_info device_id = None if config_entry_id is not None and device_info is not None: processed_dev_info = {"config_entry_id": config_entry_id} for key in ( "connections", "identifiers", "manufacturer", "model", "name", "sw_version", "via_device", ): if key in device_info: processed_dev_info[key] = device_info[key] device = device_registry.async_get_or_create(**processed_dev_info) if device: device_id = device.id disabled_by: Optional[str] = None if not entity.entity_registry_enabled_default: disabled_by = DISABLED_INTEGRATION entry = entity_registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id, config_entry=self.config_entry, device_id=device_id, known_object_ids=self.entities.keys(), disabled_by=disabled_by, capabilities=entity.capability_attributes, supported_features=entity.supported_features, device_class=entity.device_class, unit_of_measurement=entity.unit_of_measurement, ) entity.registry_entry = entry entity.entity_id = entry.entity_id if entry.disabled: self.logger.info( "Not adding entity %s because it's disabled", entry.name or entity.name or f'"{self.platform_name} {entity.unique_id}"', ) return # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif entity.entity_id is not None and entity_registry.async_is_registered( entity.entity_id ): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] entity.entity_id = None # Generate entity ID if entity.entity_id is None: suggested_object_id = ( suggested_object_id or entity.name or DEVICE_DEFAULT_NAME ) if self.entity_namespace is not None: suggested_object_id = f"{self.entity_namespace} {suggested_object_id}" entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys() ) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError(f"Invalid entity id: {entity.entity_id}") already_exists = entity.entity_id in self.entities if not already_exists: existing = self.hass.states.get(entity.entity_id) if existing and not existing.attributes.get("restored"): already_exists = True if already_exists: msg = f"Entity id already exists: {entity.entity_id}" if entity.unique_id is not None: msg += f". Platform {self.platform_name} does not generate unique IDs" raise HomeAssistantError(msg) entity_id = entity.entity_id self.entities[entity_id] = entity entity.async_on_remove(lambda: self.entities.pop(entity_id)) await entity.async_internal_added_to_hass() await entity.async_added_to_hass() await entity.async_update_ha_state()
async def _async_add_entity(self, entity, update_before_add, entity_registry, device_registry): """Add an entity to the platform.""" if entity is None: raise ValueError('Entity cannot be None') entity.hass = self.hass entity.platform = self # Async entity # PARALLEL_UPDATE == None: entity.parallel_updates = None # PARALLEL_UPDATE == 0: entity.parallel_updates = None # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) # Sync entity # PARALLEL_UPDATE == None: entity.parallel_updates = Semaphore(1) # PARALLEL_UPDATE == 0: entity.parallel_updates = None # PARALLEL_UPDATE > 0: entity.parallel_updates = Semaphore(p) if hasattr(entity, 'async_update') and not self.parallel_updates: entity.parallel_updates = None elif (not hasattr(entity, 'async_update') and self.parallel_updates == 0): entity.parallel_updates = None else: entity.parallel_updates = self._get_parallel_updates_semaphore() # Update properties before we generate the entity_id if update_before_add: try: await entity.async_device_update(warning=False) except Exception: # pylint: disable=broad-except self.logger.exception("%s: Error on device update!", self.platform_name) return suggested_object_id = None # Get entity_id from unique ID registration if entity.unique_id is not None: if entity.entity_id is not None: suggested_object_id = split_entity_id(entity.entity_id)[1] else: suggested_object_id = entity.name if self.entity_namespace is not None: suggested_object_id = '{} {}'.format(self.entity_namespace, suggested_object_id) if self.config_entry is not None: config_entry_id = self.config_entry.entry_id else: config_entry_id = None device_info = entity.device_info device_id = None if config_entry_id is not None and device_info is not None: processed_dev_info = {'config_entry_id': config_entry_id} for key in ( 'connections', 'identifiers', 'manufacturer', 'model', 'name', 'sw_version', 'via_device', ): if key in device_info: processed_dev_info[key] = device_info[key] device = device_registry.async_get_or_create( **processed_dev_info) if device: device_id = device.id entry = entity_registry.async_get_or_create( self.domain, self.platform_name, entity.unique_id, suggested_object_id=suggested_object_id, config_entry_id=config_entry_id, device_id=device_id, known_object_ids=self.entities.keys()) if entry.disabled: self.logger.info( "Not adding entity %s because it's disabled", entry.name or entity.name or '"{} {}"'.format(self.platform_name, entity.unique_id)) return entity.entity_id = entry.entity_id entity.registry_name = entry.name entity.async_on_remove(entry.add_update_listener(entity)) # We won't generate an entity ID if the platform has already set one # We will however make sure that platform cannot pick a registered ID elif (entity.entity_id is not None and entity_registry.async_is_registered(entity.entity_id)): # If entity already registered, convert entity id to suggestion suggested_object_id = split_entity_id(entity.entity_id)[1] entity.entity_id = None # Generate entity ID if entity.entity_id is None: suggested_object_id = \ suggested_object_id or entity.name or DEVICE_DEFAULT_NAME if self.entity_namespace is not None: suggested_object_id = '{} {}'.format(self.entity_namespace, suggested_object_id) entity.entity_id = entity_registry.async_generate_entity_id( self.domain, suggested_object_id, self.entities.keys()) # Make sure it is valid in case an entity set the value themselves if not valid_entity_id(entity.entity_id): raise HomeAssistantError('Invalid entity id: {}'.format( entity.entity_id)) if (entity.entity_id in self.entities or entity.entity_id in self.hass.states.async_entity_ids(self.domain)): msg = 'Entity id already exists: {}'.format(entity.entity_id) if entity.unique_id is not None: msg += '. Platform {} does not generate unique IDs'.format( self.platform_name) raise HomeAssistantError(msg) entity_id = entity.entity_id self.entities[entity_id] = entity entity.async_on_remove(lambda: self.entities.pop(entity_id)) await entity.async_added_to_hass() await entity.async_update_ha_state()