Ejemplo n.º 1
0
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))
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
    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()
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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))
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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))
Ejemplo n.º 11
0
    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)
                            ])
Ejemplo n.º 12
0
    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()
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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))
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
    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()
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
    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
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
    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()
Ejemplo n.º 26
0
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))
Ejemplo n.º 27
0
    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")
Ejemplo n.º 28
0
    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()
Ejemplo n.º 29
0
    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
Ejemplo n.º 30
0
    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
Ejemplo n.º 31
0
    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
Ejemplo n.º 32
0
 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)
Ejemplo n.º 33
0
    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()
Ejemplo n.º 34
0
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))
Ejemplo n.º 35
0
    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
Ejemplo n.º 36
0
    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()
Ejemplo n.º 37
0
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))
Ejemplo n.º 38
0
 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}")
Ejemplo n.º 39
0
 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)
Ejemplo n.º 40
0
    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()
Ejemplo n.º 41
0
    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()