221 files
This commit is contained in:
@@ -14,6 +14,7 @@ from homeassistant.components.blueprint.importer import fetch_blueprint_from_url
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -37,17 +38,26 @@ class SpookService(AbstractSpookAdminService):
|
||||
call.data["url"],
|
||||
)
|
||||
except (TimeoutError, aiohttp.ClientError) as err:
|
||||
msg = "Error fetching blueprint from URL"
|
||||
raise HomeAssistantError(msg) from err
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="blueprint_fetch_failed",
|
||||
) from err
|
||||
|
||||
if imported_blueprint is None:
|
||||
msg = "This url is not supported"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="blueprint_url_not_supported",
|
||||
)
|
||||
|
||||
domain_blueprints: dict[str, DomainBlueprints] = self.hass.data.get(DOMAIN, {})
|
||||
if imported_blueprint.blueprint.domain not in domain_blueprints:
|
||||
msg = f"Unsupported domain: {imported_blueprint.blueprint.domain}"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="blueprint_domain_not_supported",
|
||||
translation_placeholders={
|
||||
"domain": imported_blueprint.blueprint.domain,
|
||||
},
|
||||
)
|
||||
|
||||
imported_blueprint.blueprint.update_metadata(source_url=call.data["url"])
|
||||
|
||||
@@ -59,8 +69,12 @@ class SpookService(AbstractSpookAdminService):
|
||||
imported_blueprint.suggested_filename,
|
||||
)
|
||||
except FileAlreadyExists as ex:
|
||||
msg = "File already exists"
|
||||
raise HomeAssistantError(msg) from ex
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="blueprint_file_exists",
|
||||
) from ex
|
||||
except OSError as err:
|
||||
msg = "Error writing file"
|
||||
raise HomeAssistantError(msg) from err
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="blueprint_write_failed",
|
||||
) from err
|
||||
|
||||
@@ -7,11 +7,11 @@ from homeassistant.const import (
|
||||
EVENT_COMPONENT_LOADED,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import DATA_ENTITY_PLATFORM, EntityPlatform
|
||||
|
||||
from ....const import LOGGER
|
||||
from ....entity_filtering import async_filter_known_entity_ids, async_get_all_entity_ids
|
||||
from ....entity_suggestions import async_describe_unknown_entities
|
||||
from ....helper_sources import async_group_members
|
||||
from ....repairs import AbstractSpookRepair
|
||||
|
||||
|
||||
@@ -35,55 +35,35 @@ class SpookRepair(AbstractSpookRepair):
|
||||
|
||||
known_entity_ids = async_get_all_entity_ids(self.hass)
|
||||
|
||||
platforms: list[EntityPlatform] | None
|
||||
if not (platforms := self.hass.data[DATA_ENTITY_PLATFORM].get(self.domain)):
|
||||
return # Nothing to do.
|
||||
for entity, members in async_group_members(self.hass):
|
||||
self.possible_issue_ids.add(entity.entity_id)
|
||||
|
||||
# Taken as a snapshot: describing what is missing can hand the event
|
||||
# loop a turn, and a change to the live collection during it ends the
|
||||
# inspection in a `RuntimeError`. #1558.
|
||||
for platform in list(platforms):
|
||||
# We don't want to check the old style group platform
|
||||
for entity in list(platform.entities.values()):
|
||||
self.possible_issue_ids.add(entity.entity_id)
|
||||
members = []
|
||||
if platform.domain == group.DOMAIN:
|
||||
members = entity.tracking
|
||||
elif hasattr(entity, "_entity_ids"):
|
||||
# pylint: disable-next=protected-access
|
||||
members = entity._entity_ids # noqa: SLF001
|
||||
elif hasattr(entity, "_entities"):
|
||||
# pylint: disable-next=protected-access
|
||||
members = entity._entities # noqa: SLF001
|
||||
|
||||
if unknown_entities := async_filter_known_entity_ids(
|
||||
self.hass, entity_ids=members, known_entity_ids=known_entity_ids
|
||||
):
|
||||
described = await async_describe_unknown_entities(
|
||||
self.hass, sorted(unknown_entities)
|
||||
)
|
||||
self.async_create_issue(
|
||||
issue_id=entity.entity_id,
|
||||
references=unknown_entities,
|
||||
is_fixable=True,
|
||||
data={
|
||||
"group_entity_id": entity.entity_id,
|
||||
# What the fix checks again before dropping any.
|
||||
"group_unknown_entity_ids": ",".join(
|
||||
sorted(unknown_entities)
|
||||
),
|
||||
"group": entity.name,
|
||||
"entities": described,
|
||||
},
|
||||
translation_placeholders={
|
||||
"entities": described,
|
||||
"group": entity.name,
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
)
|
||||
LOGGER.debug(
|
||||
"Spook found unknown member entities in %s "
|
||||
"and created an issue for it; Entities: %s",
|
||||
entity.entity_id,
|
||||
", ".join(unknown_entities),
|
||||
)
|
||||
if unknown_entities := async_filter_known_entity_ids(
|
||||
self.hass, entity_ids=members, known_entity_ids=known_entity_ids
|
||||
):
|
||||
described = await async_describe_unknown_entities(
|
||||
self.hass, sorted(unknown_entities)
|
||||
)
|
||||
self.async_create_issue(
|
||||
issue_id=entity.entity_id,
|
||||
references=unknown_entities,
|
||||
is_fixable=True,
|
||||
data={
|
||||
"group_entity_id": entity.entity_id,
|
||||
# What the fix checks again before dropping any.
|
||||
"group_unknown_entity_ids": ",".join(sorted(unknown_entities)),
|
||||
"group": entity.name,
|
||||
"entities": described,
|
||||
},
|
||||
translation_placeholders={
|
||||
"entities": described,
|
||||
"group": entity.name,
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
)
|
||||
LOGGER.debug(
|
||||
"Spook found unknown member entities in %s "
|
||||
"and created an issue for it; Entities: %s",
|
||||
entity.entity_id,
|
||||
", ".join(unknown_entities),
|
||||
)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components import homeassistant
|
||||
from homeassistant.const import __version__
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.loader import async_get_loaded_integration
|
||||
|
||||
from ...const import DOMAIN
|
||||
from ...entity import SpookEntity, SpookEntityDescription
|
||||
@@ -16,10 +16,21 @@ class HomeAssistantSpookEntity(SpookEntity):
|
||||
def __init__(self, description: SpookEntityDescription) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(description=description)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, homeassistant.DOMAIN)},
|
||||
manufacturer="Home Assistant",
|
||||
name="Home Assistant",
|
||||
sw_version=__version__,
|
||||
)
|
||||
self._attr_unique_id = f"{homeassistant.DOMAIN}_{description.key}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device, which is Spook's, so it runs Spook's version.
|
||||
|
||||
Read once the entity is added to Home Assistant, as the version comes
|
||||
from the loaded integration.
|
||||
"""
|
||||
version = async_get_loaded_integration(self.hass, DOMAIN).version
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, homeassistant.DOMAIN)},
|
||||
manufacturer="Spook 👻",
|
||||
name="Home Assistant",
|
||||
sw_version=str(version) if version else None,
|
||||
configuration_url="https://spook.boo/devices-entities",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
)
|
||||
|
||||
@@ -13,9 +13,12 @@ from homeassistant.components.homeassistant.exposed_entities import (
|
||||
async_expose_entity,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
|
||||
from ....const import DOMAIN
|
||||
from ....errors import entity_not_found
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
|
||||
@@ -41,13 +44,20 @@ def async_config_entry_ids(hass: HomeAssistant, call: ServiceCall) -> list[str]:
|
||||
domains: list[str] = call.data.get(CONF_DOMAIN, [])
|
||||
|
||||
if not entry_ids and not domains:
|
||||
msg = "Name the integration entries to change, or their integration"
|
||||
raise ServiceValidationError(msg)
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="integration_entries_required",
|
||||
)
|
||||
|
||||
for domain in domains:
|
||||
if not (entries := hass.config_entries.async_entries(domain)):
|
||||
msg = f"The {domain} integration has no entries"
|
||||
raise ServiceValidationError(msg)
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="integration_without_entries",
|
||||
translation_placeholders={
|
||||
"domain": domain,
|
||||
},
|
||||
)
|
||||
entry_ids.extend(
|
||||
entry.entry_id for entry in entries if entry.entry_id not in entry_ids
|
||||
)
|
||||
@@ -82,8 +92,7 @@ def async_set_voice_assistant_exposure(
|
||||
hass.states.get(entity_id) is None
|
||||
and entity_registry.async_get(entity_id) is None
|
||||
):
|
||||
msg = f"Unknown entity: {entity_id}"
|
||||
raise HomeAssistantError(msg)
|
||||
raise entity_not_found(entity_id)
|
||||
|
||||
for entity_id in entity_ids:
|
||||
for assistant in call.data[CONF_ASSISTANTS]:
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry as ar, config_validation as cv
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
area_registry = ar.async_get(self.hass)
|
||||
if not (area := area_registry.async_get_area(call.data["area_id"])):
|
||||
msg = f"Area {call.data['area_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(call.data["area_id"])
|
||||
|
||||
aliases = area.aliases.copy()
|
||||
area_registry.async_update(
|
||||
|
||||
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
floor_registry as fr,
|
||||
)
|
||||
|
||||
from ....errors import floor_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -33,8 +33,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
floor_registry = fr.async_get(self.hass)
|
||||
if not (floor := floor_registry.async_get_floor(call.data["floor_id"])):
|
||||
msg = f"Floor {call.data['floor_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise floor_not_found(call.data["floor_id"])
|
||||
|
||||
aliases = floor.aliases.copy()
|
||||
floor_registry.async_update(
|
||||
|
||||
@@ -7,13 +7,13 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
floor_registry as fr,
|
||||
)
|
||||
|
||||
from ....errors import floor_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -34,8 +34,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
floor_registry = fr.async_get(self.hass)
|
||||
if not floor_registry.async_get_floor(call.data["floor_id"]):
|
||||
msg = f"Floor {call.data['floor_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise floor_not_found(call.data["floor_id"])
|
||||
|
||||
area_registry = ar.async_get(self.hass)
|
||||
for area_id in call.data["area_id"]:
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
@@ -15,6 +14,7 @@ from homeassistant.helpers import (
|
||||
)
|
||||
|
||||
from ....core_compat import async_update_any_device
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -35,8 +35,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
area_registry = ar.async_get(self.hass)
|
||||
if not area_registry.async_get_area(call.data["area_id"]):
|
||||
msg = f"Area {call.data['area_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(call.data["area_id"])
|
||||
|
||||
device_registry = dr.async_get(self.hass)
|
||||
for device_id in call.data["device_id"]:
|
||||
|
||||
@@ -7,13 +7,13 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
entity_registry as er,
|
||||
)
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -34,8 +34,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
area_registry = ar.async_get(self.hass)
|
||||
if not area_registry.async_get_area(call.data["area_id"]):
|
||||
msg = f"Area {call.data['area_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(call.data["area_id"])
|
||||
|
||||
entity_registry = er.async_get(self.hass)
|
||||
for entity_id in call.data["entity_id"]:
|
||||
|
||||
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
)
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -41,8 +41,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for area_id in call.data["area_id"]:
|
||||
if (area_entry := area_registry.async_get_area(area_id)) is None:
|
||||
msg = f"Area {area_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(area_id)
|
||||
|
||||
labels = area_entry.labels.copy()
|
||||
labels.update(call.data["label_id"])
|
||||
|
||||
@@ -7,13 +7,13 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
)
|
||||
|
||||
from ....core_compat import async_update_any_device
|
||||
from ....errors import device_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -42,8 +42,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for device_id in call.data["device_id"]:
|
||||
if (device_entry := device_registry.async_get(device_id)) is None:
|
||||
msg = f"Device {device_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise device_not_found(device_id)
|
||||
|
||||
labels = device_entry.labels.copy()
|
||||
labels.update(call.data["label_id"])
|
||||
|
||||
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
entity_registry as er,
|
||||
)
|
||||
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -41,8 +41,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for entity_id in call.data["entity_id"]:
|
||||
if (entity_entry := entity_registry.async_get(entity_id)) is None:
|
||||
msg = f"Entity {entity_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise entity_not_found(entity_id)
|
||||
|
||||
labels = entity_entry.labels.copy()
|
||||
labels.update(call.data["label_id"])
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....errors import config_entry_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
call.data["config_entry_id"],
|
||||
)
|
||||
):
|
||||
msg = f"Config entry not found: {call.data['config_entry_id']}"
|
||||
raise HomeAssistantError(msg)
|
||||
raise config_entry_not_found(call.data["config_entry_id"])
|
||||
|
||||
self.hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
|
||||
@@ -10,6 +10,8 @@ from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import user_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -31,11 +33,15 @@ class SpookService(AbstractSpookAdminService):
|
||||
for user_id in call.data["user_id"]:
|
||||
user = await self.hass.auth.async_get_user(user_id)
|
||||
if user is None:
|
||||
message = f"Could not find user: {user_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise user_not_found(user_id)
|
||||
if user.system_generated:
|
||||
message = f"Cannot disable a system-generated user: {user_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="cannot_disable_system_user",
|
||||
translation_placeholders={
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
users.append(user)
|
||||
|
||||
for user in users:
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....errors import config_entry_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
call.data["config_entry_id"],
|
||||
)
|
||||
):
|
||||
msg = f"Config entry not found: {call.data['config_entry_id']}"
|
||||
raise HomeAssistantError(msg)
|
||||
raise config_entry_not_found(call.data["config_entry_id"])
|
||||
|
||||
self.hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
|
||||
@@ -10,6 +10,8 @@ from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import user_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -31,11 +33,15 @@ class SpookService(AbstractSpookAdminService):
|
||||
for user_id in call.data["user_id"]:
|
||||
user = await self.hass.auth.async_get_user(user_id)
|
||||
if user is None:
|
||||
message = f"Could not find user: {user_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise user_not_found(user_id)
|
||||
if user.system_generated:
|
||||
message = f"Cannot enable a system-generated user: {user_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="cannot_enable_system_user",
|
||||
translation_placeholders={
|
||||
"user_id": user_id,
|
||||
},
|
||||
)
|
||||
users.append(user)
|
||||
|
||||
for user in users:
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry as ar, config_validation as cv
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
area_registry = ar.async_get(self.hass)
|
||||
if not (area := area_registry.async_get_area(call.data["area_id"])):
|
||||
msg = f"Area {call.data['area_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(call.data["area_id"])
|
||||
|
||||
aliases = area.aliases.copy()
|
||||
area_registry.async_update(
|
||||
|
||||
+2
-3
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, floor_registry as fr
|
||||
|
||||
from ....errors import floor_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
floor_registry = fr.async_get(self.hass)
|
||||
if not (floor := floor_registry.async_get_floor(call.data["floor_id"])):
|
||||
msg = f"Floor {call.data['floor_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise floor_not_found(call.data["floor_id"])
|
||||
|
||||
aliases = floor.aliases.copy()
|
||||
floor_registry.async_update(
|
||||
|
||||
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
)
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -41,8 +41,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for area_id in call.data["area_id"]:
|
||||
if (area_entry := area_registry.async_get_area(area_id)) is None:
|
||||
msg = f"Area {area_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(area_id)
|
||||
|
||||
labels = area_entry.labels.copy()
|
||||
labels.difference_update(call.data["label_id"])
|
||||
|
||||
+2
-3
@@ -7,13 +7,13 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
)
|
||||
|
||||
from ....core_compat import async_update_any_device
|
||||
from ....errors import device_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -42,8 +42,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for device_id in call.data["device_id"]:
|
||||
if (device_entry := device_registry.async_get(device_id)) is None:
|
||||
msg = f"Device {device_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise device_not_found(device_id)
|
||||
|
||||
labels = device_entry.labels.copy()
|
||||
labels.difference_update(call.data["label_id"])
|
||||
|
||||
+2
-3
@@ -7,12 +7,12 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
entity_registry as er,
|
||||
)
|
||||
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
from ..labels import async_check_labels_exist
|
||||
|
||||
@@ -41,8 +41,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
updates: dict[str, set[str]] = {}
|
||||
for entity_id in call.data["entity_id"]:
|
||||
if (entity_entry := entity_registry.async_get(entity_id)) is None:
|
||||
msg = f"Entity {entity_id} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise entity_not_found(entity_id)
|
||||
|
||||
labels = entity_entry.labels.copy()
|
||||
labels.difference_update(call.data["label_id"])
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.const import RESTART_EXIT_CODE
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import LOGGER
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN, LOGGER
|
||||
from ....services import AbstractSpookAdminService, ReplaceExistingService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -42,9 +42,9 @@ class SpookService(AbstractSpookAdminService, ReplaceExistingService):
|
||||
return
|
||||
|
||||
if not self.overriden_service:
|
||||
msg = "Spook encountered an error while restarting Home Assistant."
|
||||
raise HomeAssistantError(
|
||||
msg,
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="restart_failed",
|
||||
)
|
||||
|
||||
# Waited on, the way calling Home Assistant's own restart is. It
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import area_registry as ar, config_validation as cv
|
||||
|
||||
from ....errors import area_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
area_registry = ar.async_get(self.hass)
|
||||
if not area_registry.async_get_area(call.data["area_id"]):
|
||||
msg = f"Area {call.data['area_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise area_not_found(call.data["area_id"])
|
||||
|
||||
area_registry.async_update(
|
||||
call.data["area_id"],
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.homeassistant import DOMAIN
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, floor_registry as fr
|
||||
|
||||
from ....errors import floor_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -30,8 +30,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
"""Handle the service call."""
|
||||
floor_registry = fr.async_get(self.hass)
|
||||
if not floor_registry.async_get_floor(call.data["floor_id"]):
|
||||
msg = f"Floor {call.data['floor_id']} not found"
|
||||
raise HomeAssistantError(msg)
|
||||
raise floor_not_found(call.data["floor_id"])
|
||||
|
||||
floor_registry.async_update(
|
||||
call.data["floor_id"],
|
||||
|
||||
@@ -10,6 +10,8 @@ import voluptuous as vol
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.input_number import InputNumber
|
||||
from homeassistant.core import ServiceCall
|
||||
@@ -31,8 +33,14 @@ def native_value_as_float(entity: InputNumber) -> float:
|
||||
try:
|
||||
return float(entity.native_value)
|
||||
except (TypeError, ValueError) as err:
|
||||
msg = f"Value {entity.native_value!r} for {entity.entity_id} is not a number"
|
||||
raise HomeAssistantError(msg) from err
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="value_not_a_number",
|
||||
translation_placeholders={
|
||||
"value": repr(entity.native_value),
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
) from err
|
||||
|
||||
|
||||
def step_amount(entity: InputNumber, call: ServiceCall) -> float:
|
||||
|
||||
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ....const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components.number import NumberEntity
|
||||
|
||||
@@ -21,8 +23,14 @@ def shown_value_as_float(entity: NumberEntity) -> float:
|
||||
try:
|
||||
return float(entity.value)
|
||||
except (TypeError, ValueError) as err:
|
||||
msg = f"Value {entity.value!r} for {entity.entity_id} is not a number"
|
||||
raise HomeAssistantError(msg) from err
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="value_not_a_number",
|
||||
translation_placeholders={
|
||||
"value": repr(entity.value),
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
) from err
|
||||
|
||||
|
||||
async def async_set_shown_value(entity: NumberEntity, value: float) -> None:
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
||||
from homeassistant.components.number import DOMAIN, NumberEntity
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....services import AbstractSpookEntityComponentService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -26,6 +27,11 @@ class SpookService(AbstractSpookEntityComponentService[NumberEntity]):
|
||||
) -> None:
|
||||
"""Handle the service call."""
|
||||
if entity.max_value is None:
|
||||
msg = f"Entity {entity.entity_id} has no max value"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="no_max_value",
|
||||
translation_placeholders={
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
)
|
||||
await entity.async_set_native_value(entity.native_max_value)
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING
|
||||
from homeassistant.components.number import DOMAIN, NumberEntity
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....services import AbstractSpookEntityComponentService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -26,6 +27,11 @@ class SpookService(AbstractSpookEntityComponentService[NumberEntity]):
|
||||
) -> None:
|
||||
"""Handle the service call."""
|
||||
if entity.min_value is None:
|
||||
msg = f"Entity {entity.entity_id} has no min value"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="no_min_value",
|
||||
translation_placeholders={
|
||||
"entity_id": entity.entity_id,
|
||||
},
|
||||
)
|
||||
await entity.async_set_native_value(entity.native_min_value)
|
||||
|
||||
@@ -11,6 +11,8 @@ from homeassistant.components.person import DOMAIN, Person, PersonStorageCollect
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -38,13 +40,17 @@ class SpookService(AbstractSpookAdminService):
|
||||
_, collection, entity_component = self.hass.data[DOMAIN]
|
||||
|
||||
if not (entity := entity_component.get_entity(call.data["entity_id"])):
|
||||
message = f"Could not find entity_id: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise entity_not_found(call.data["entity_id"])
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
if not entity.editable or "id" not in entity._config: # noqa: SLF001
|
||||
message = f"This person is not editable: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="person_not_editable",
|
||||
translation_placeholders={
|
||||
"entity_id": call.data["entity_id"],
|
||||
},
|
||||
)
|
||||
|
||||
await collection.async_update_item(
|
||||
# pylint: disable-next=protected-access
|
||||
|
||||
@@ -11,6 +11,8 @@ from homeassistant.components.person import DOMAIN, Person, PersonStorageCollect
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -38,13 +40,17 @@ class SpookService(AbstractSpookAdminService):
|
||||
_, collection, entity_component = self.hass.data[DOMAIN]
|
||||
|
||||
if not (entity := entity_component.get_entity(call.data["entity_id"])):
|
||||
message = f"Could not find entity_id: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise entity_not_found(call.data["entity_id"])
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
if not entity.editable or "id" not in entity._config: # noqa: SLF001
|
||||
message = f"This person is not editable: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="person_not_editable",
|
||||
translation_placeholders={
|
||||
"entity_id": call.data["entity_id"],
|
||||
},
|
||||
)
|
||||
|
||||
await collection.async_update_item(
|
||||
# pylint: disable-next=protected-access
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components import repairs
|
||||
from homeassistant.const import __version__
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.loader import async_get_loaded_integration
|
||||
|
||||
from ...const import DOMAIN
|
||||
from ...entity import SpookEntity, SpookEntityDescription
|
||||
@@ -16,10 +16,21 @@ class RepairsSpookEntity(SpookEntity):
|
||||
def __init__(self, description: SpookEntityDescription) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(description=description)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, repairs.DOMAIN)},
|
||||
manufacturer="Home Assistant",
|
||||
name="Repairs",
|
||||
sw_version=__version__,
|
||||
)
|
||||
self._attr_unique_id = f"{repairs.DOMAIN}_{description.key}"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device, which is Spook's, so it runs Spook's version.
|
||||
|
||||
Read once the entity is added to Home Assistant, as the version comes
|
||||
from the loaded integration.
|
||||
"""
|
||||
version = async_get_loaded_integration(self.hass, DOMAIN).version
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, repairs.DOMAIN)},
|
||||
manufacturer="Spook 👻",
|
||||
name="Repairs",
|
||||
sw_version=str(version) if version else None,
|
||||
configuration_url="https://spook.boo/repairs",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
)
|
||||
|
||||
@@ -21,5 +21,7 @@ class SpookService(AbstractSpookService):
|
||||
|
||||
async def async_handle_service(self, _: ServiceCall) -> None:
|
||||
"""Handle the service call."""
|
||||
msg = "Spooked!"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="spooked",
|
||||
)
|
||||
|
||||
@@ -23,5 +23,7 @@ class SpookService(AbstractSpookService):
|
||||
async def async_handle_service(self, _: ServiceCall) -> None:
|
||||
"""Handle the service call."""
|
||||
if random.choice([True, False]): # noqa: S311
|
||||
msg = "Spooked!"
|
||||
raise HomeAssistantError(msg)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="spooked",
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.const import CONF_ID
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....services import AbstractSpookEntityComponentService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -45,8 +46,13 @@ class SpookService(AbstractSpookEntityComponentService[Timer]):
|
||||
entity_id = entity.entity_id
|
||||
|
||||
if not entity.editable or not entity.unique_id:
|
||||
message = f"This timer is not editable: {entity_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="timer_not_editable",
|
||||
translation_placeholders={
|
||||
"entity_id": entity_id,
|
||||
},
|
||||
)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
updates = entity._config.copy() # noqa: SLF001
|
||||
|
||||
@@ -11,6 +11,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_component import DATA_INSTANCES, EntityComponent
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -46,13 +48,17 @@ class SpookService(AbstractSpookAdminService):
|
||||
zone_ids = []
|
||||
for entity_id in call.data["entity_id"]:
|
||||
if not (entity := entity_component.get_entity(entity_id)):
|
||||
message = f"Could not find entity_id: {entity_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise entity_not_found(entity_id)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
if not entity.editable or "id" not in entity._config: # noqa: SLF001
|
||||
message = f"This zone is not editable: {entity_id}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="zone_not_editable",
|
||||
translation_placeholders={
|
||||
"entity_id": entity_id,
|
||||
},
|
||||
)
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
zone_ids.append(entity._config["id"]) # noqa: SLF001
|
||||
|
||||
@@ -18,6 +18,8 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_component import DATA_INSTANCES, EntityComponent
|
||||
|
||||
from ....const import DOMAIN as SPOOK_DOMAIN
|
||||
from ....errors import entity_not_found
|
||||
from ....services import AbstractSpookAdminService
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -65,8 +67,7 @@ class SpookService(AbstractSpookAdminService):
|
||||
].__self__.storage_collection
|
||||
|
||||
if not (entity := entity_component.get_entity(call.data["entity_id"])):
|
||||
message = f"Could not find entity_id: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise entity_not_found(call.data["entity_id"])
|
||||
|
||||
if _is_the_house(entity):
|
||||
await self._async_update_home(call)
|
||||
@@ -74,8 +75,13 @@ class SpookService(AbstractSpookAdminService):
|
||||
|
||||
# pylint: disable-next=protected-access
|
||||
if not entity.editable or "id" not in entity._config: # noqa: SLF001
|
||||
message = f"This zone is not editable: {call.data['entity_id']}"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="zone_not_editable",
|
||||
translation_placeholders={
|
||||
"entity_id": call.data["entity_id"],
|
||||
},
|
||||
)
|
||||
|
||||
data = call.data.copy()
|
||||
data.pop("entity_id")
|
||||
@@ -98,11 +104,11 @@ class SpookService(AbstractSpookAdminService):
|
||||
data.pop("entity_id")
|
||||
|
||||
if unsupported := set(data) - {CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS}:
|
||||
message = (
|
||||
"The home zone only takes a latitude, longitude and radius, "
|
||||
f"not: {', '.join(sorted(unsupported))}"
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="home_zone_unsupported",
|
||||
translation_placeholders={"fields": ", ".join(sorted(unsupported))},
|
||||
)
|
||||
raise HomeAssistantError(message)
|
||||
|
||||
# Home Assistant keeps the radius of the house in whole meters, and
|
||||
# never below zero. Cutting a fraction off quietly would be a
|
||||
@@ -111,8 +117,10 @@ class SpookService(AbstractSpookAdminService):
|
||||
if CONF_RADIUS in data:
|
||||
radius = data[CONF_RADIUS]
|
||||
if radius != int(radius) or radius < 0:
|
||||
message = "The home zone takes its radius in whole meters, zero or more"
|
||||
raise HomeAssistantError(message)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=SPOOK_DOMAIN,
|
||||
translation_key="home_zone_radius",
|
||||
)
|
||||
data[CONF_RADIUS] = int(radius)
|
||||
|
||||
await self.hass.config.async_update(**data)
|
||||
|
||||
Reference in New Issue
Block a user