Exemplo n.º 1
0
def test_valid_entity_id():
    """Test valid entity ID."""
    for invalid in [
            "_light.kitchen",
            ".kitchen",
            ".light.kitchen",
            "light_.kitchen",
            "light._kitchen",
            "light.",
            "light.kitchen__ceiling",
            "light.kitchen_yo_",
            "light.kitchen.",
            "Light.kitchen",
            "light.Kitchen",
            "lightkitchen",
    ]:
        assert not ha.valid_entity_id(invalid), invalid

    for valid in [
            "1.a",
            "1light.kitchen",
            "a.1",
            "a.a",
            "input_boolean.hello_world_0123",
            "light.1kitchen",
            "light.kitchen",
            "light.something_yoo",
    ]:
        assert ha.valid_entity_id(valid), valid
Exemplo n.º 2
0
 def __getattr__(self, name):
     """Return the domain state."""
     if "." in name:
         if not valid_entity_id(name):
             raise TemplateError(f"Invalid entity ID '{name}'")
         return _get_state(self._opp, name)
     if not valid_entity_id(f"{name}.entity"):
         raise TemplateError(f"Invalid domain name '{name}'")
     return DomainStates(self._opp, name)
Exemplo n.º 3
0
def entity_id(value: Any) -> str:
    """Validate Entity ID."""
    str_value = string(value).lower()
    if valid_entity_id(str_value):
        return str_value

    raise vol.Invalid(f"Entity ID {value} is an invalid entity ID")
            async def load_instance(opp: OpenPeerPower) -> "RestoreStateData":
                """Set up the restore state helper."""
                data = cls(opp)

                try:
                    stored_states = await data.store.async_load()
                except OpenPeerPowerError as exc:
                    _LOGGER.error("Error loading last states", exc_info=exc)
                    stored_states = None

                if stored_states is None:
                    _LOGGER.debug("Not creating cache - no saved states found")
                    data.last_states = {}
                else:
                    data.last_states = {
                        item["state"]["entity_id"]: StoredState.from_dict(item)
                        for item in stored_states
                        if valid_entity_id(item["state"]["entity_id"])
                    }
                    _LOGGER.debug("Created cache with %s",
                                  list(data.last_states))

                if opp.state == CoreState.running:
                    data.async_setup_dump()
                else:
                    opp.bus.async_listen_once(EVENT_OPENPEERPOWER_START,
                                              data.async_setup_dump)

                return data
Exemplo n.º 5
0
def extract_entities(
        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 = _RE_GET_ENTITIES.findall(template)
    extraction_final = []

    for result in extraction:
        if (result[0] == "trigger.entity_id" and variables
                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
Exemplo n.º 6
0
def service(value: Any) -> str:
    """Validate service."""
    # Services use same format as entities so we can use same helper.
    str_value = string(value).lower()
    if valid_entity_id(str_value):
        return str_value
    raise vol.Invalid(f"Service {value} does not match format <domain>.<name>")
Exemplo n.º 7
0
async def test_loading_invalid_entity_id(opp, opp_storage):
    """Test we autofix invalid entity IDs."""
    opp_storage[er.STORAGE_KEY] = {
        "version": er.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 = er.async_get(opp)

    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)
Exemplo n.º 8
0
def distance(opp, *args):
    """Calculate distance.

    Will calculate distance from home to a point or between points.
    Points can be passed in using state objects or lat/lng coordinates.
    """
    locations = []

    to_process = list(args)

    while to_process:
        value = to_process.pop(0)
        if isinstance(value, str) and not valid_entity_id(value):
            point_state = None
        else:
            point_state = _resolve_state(opp, value)

        if point_state is None:
            # We expect this and next value to be lat&lng
            if not to_process:
                _LOGGER.warning(
                    "Distance:Expected latitude and longitude, got %s", value)
                return None

            value_2 = to_process.pop(0)
            latitude = convert(value, float)
            longitude = convert(value_2, float)

            if latitude is None or longitude is None:
                _LOGGER.warning(
                    "Distance:Unable to process latitude and longitude: %s, %s",
                    value,
                    value_2,
                )
                return None

        else:
            if not loc_helper.has_location(point_state):
                _LOGGER.warning(
                    "Distance:State does not contain valid location: %s",
                    point_state)
                return None

            latitude = point_state.attributes.get(ATTR_LATITUDE)
            longitude = point_state.attributes.get(ATTR_LONGITUDE)

        locations.append((latitude, longitude))

    if len(locations) == 1:
        return opp.config.distance(*locations[0])

    return opp.config.units.length(
        loc_util.distance(*locations[0] + locations[1]), LENGTH_METERS)
Exemplo n.º 9
0
    def __getattr__(self, name):
        """Return the domain state."""
        if "." in name:
            return _get_state_if_valid(self._opp, name)

        if name in _RESERVED_NAMES:
            return None

        if not valid_entity_id(f"{name}.entity"):
            raise TemplateError(f"Invalid domain name '{name}'")

        return DomainStates(self._opp, name)
Exemplo n.º 10
0
    async def async_load(self) -> None:
        """Load the entity registry."""
        async_setup_entity_restore(self.opp, self)

        data = await self.opp.helpers.storage.async_migrator(
            self.opp.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"),
                )

        self.entities = entities
        self._rebuild_index()
Exemplo n.º 11
0
def _get_state_if_valid(opp: OpenPeerPower,
                        entity_id: str) -> TemplateState | None:
    state = opp.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(opp, entity_id, state)
Exemplo n.º 12
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: Mapping[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.opp.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data)

        return new
Exemplo n.º 13
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._opp, entity_id)
    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.opp.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data)

        return new
Exemplo n.º 15
0
async def valid_entity_id(opp):
    """Run valid entity ID a million times."""
    start = timer()
    for _ in range(10 ** 6):
        core.valid_entity_id("light.kitchen")
    return timer() - start
Exemplo n.º 16
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.opp,
            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: 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

            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",
                        "suggested_area",
                ):
                    if key in device_info:
                        processed_dev_info[key] = device_info[
                            key]  # type: ignore[misc]

                try:
                    device = device_registry.async_get_or_create(
                        **processed_dev_info)  # type: ignore[arg-type]
                    device_id = device.id
                except RequiredParameterMissing:
                    pass

            disabled_by: str | None = 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]
            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 OpenPeerPowerError(f"Invalid entity ID: {entity.entity_id}")

        already_exists = entity.entity_id in self.entities
        restored = False

        if not already_exists and not self.opp.states.async_available(
                entity.entity_id):
            existing = self.opp.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.opp.states.async_reserve(entity.entity_id)

        def remove_entity_cb() -> None:
            """Remove entity from entities list."""
            self.entities.pop(entity_id)

        entity.async_on_remove(remove_entity_cb)

        await entity.add_to_platform_finish()
Exemplo n.º 17
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.opp = self.opp
        entity.platform = self
        entity.parallel_updates = 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)
                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,
                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}"',
                )
                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 OpenPeerPowerError(f"Invalid entity id: {entity.entity_id}")

        already_exists = entity.entity_id in self.entities

        if not already_exists:
            existing = self.opp.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 OpenPeerPowerError(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_opp()
        await entity.async_added_to_opp()

        await entity.async_update_op_state()