Updated apps

This commit is contained in:
2026-07-20 22:52:35 -04:00
parent 28a8cb98f6
commit a0c3271743
1164 changed files with 94781 additions and 6892 deletions
@@ -0,0 +1 @@
"""Spook - Your homie."""
@@ -0,0 +1,87 @@
"""Spook - Your homie."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.components.homeassistant import (
DOMAIN,
SERVICE_HOMEASSISTANT_RESTART,
SERVICE_RELOAD_ALL,
)
from homeassistant.const import EntityCategory
from ...entity import SpookEntityDescription
from .entity import HomeAssistantSpookEntity
if TYPE_CHECKING:
from collections.abc import Awaitable, Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@dataclass(frozen=True, kw_only=True)
class HomeAssistantSpookButtonEntityDescription(
SpookEntityDescription,
ButtonEntityDescription,
):
"""Class describing Spook Home Assistant button entities."""
press_fn: Callable[[HomeAssistant], Awaitable[Any]]
BUTTONS: tuple[HomeAssistantSpookButtonEntityDescription, ...] = (
HomeAssistantSpookButtonEntityDescription(
key="restart",
translation_key="homeassistant_restart",
entity_id="button.homeassistant_restart",
device_class=ButtonDeviceClass.RESTART,
entity_category=EntityCategory.CONFIG,
press_fn=lambda hass: hass.services.async_call(
DOMAIN,
SERVICE_HOMEASSISTANT_RESTART,
blocking=True,
),
),
HomeAssistantSpookButtonEntityDescription(
key="reload",
translation_key="homeassistant_reload",
entity_id="button.homeassistant_reload",
icon="mdi:auto-fix",
entity_category=EntityCategory.CONFIG,
press_fn=lambda hass: hass.services.async_call(
DOMAIN,
SERVICE_RELOAD_ALL,
blocking=True,
),
),
)
async def async_setup_entry(
_hass: HomeAssistant,
_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Spook sensor."""
async_add_entities(
HomeAssistantSpookButtonEntity(description) for description in BUTTONS
)
class HomeAssistantSpookButtonEntity(HomeAssistantSpookEntity, ButtonEntity):
"""Spook button providig Home Asistant actions."""
entity_description: HomeAssistantSpookButtonEntityDescription
async def async_press(self) -> None:
"""Press the button."""
await self.entity_description.press_fn(self.hass)
@@ -0,0 +1,55 @@
"""Device helpers for Home Assistant services."""
from __future__ import annotations
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
@callback
def async_disable_device_and_parent_if_needed(
device_registry: dr.DeviceRegistry,
device_id: str,
) -> None:
"""Disable a device and its parent when no enabled child devices remain."""
device = device_registry.async_get(device_id)
if device is None:
return
if device.disabled_by is None:
device_registry.async_update_device(
device_id=device_id,
disabled_by=dr.DeviceEntryDisabler.USER,
)
if device.via_device_id is None:
return
if all(
child.id == device_id or child.disabled_by is not None
for child in device_registry.devices.values()
if child.via_device_id == device.via_device_id
):
async_disable_device_and_parent_if_needed(
device_registry,
device.via_device_id,
)
@callback
def async_enable_device_and_parent(
device_registry: dr.DeviceRegistry,
device_id: str,
) -> None:
"""Enable a device and its parent device chain."""
device = device_registry.async_get(device_id)
if device is None:
return
if device.via_device_id is not None:
async_enable_device_and_parent(device_registry, device.via_device_id)
device_registry.async_update_device(
device_id=device_id,
disabled_by=None,
)
@@ -0,0 +1,25 @@
"""Spook - Your homie."""
from __future__ import annotations
from homeassistant.components import homeassistant
from homeassistant.const import __version__
from homeassistant.helpers.device_registry import DeviceInfo
from ...const import DOMAIN
from ...entity import SpookEntity, SpookEntityDescription
class HomeAssistantSpookEntity(SpookEntity):
"""Defines an base Spook entity for Home Assistant related entities."""
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}"
@@ -0,0 +1,644 @@
"""Spook - Your homie."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
from homeassistant.components import (
automation,
input_boolean,
input_button,
input_datetime,
input_number,
input_select,
input_text,
persistent_notification,
person,
script,
sun,
zone,
)
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
EVENT_COMPONENT_LOADED,
EVENT_HOMEASSISTANT_STARTED,
EntityCategory,
Platform,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.event import async_call_later
from ...entity import SpookEntityDescription
from ...listeners import async_listen_once_tracked
from .entity import HomeAssistantSpookEntity
if TYPE_CHECKING:
from collections.abc import Callable
from datetime import datetime # Moved datetime here
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.event_type import EventType
@dataclass(frozen=True, kw_only=True)
class HomeAssistantSpookSensorEntityDescription(
SpookEntityDescription,
SensorEntityDescription,
):
"""Class describing Spook Home Assistant sensor entities."""
value_fn: Callable[[HomeAssistant], int | None]
update_events: set[EventType[Any] | str] = field(default_factory=set)
@callback
def _count_active_domain_entities(hass: HomeAssistant, domain: str) -> int:
"""Count domain entities that are not restored placeholders."""
return sum(
(state := hass.states.get(entity_id)) is not None
and not state.attributes.get("restored", False)
for entity_id in hass.states.async_entity_ids(domain)
)
SENSORS: tuple[HomeAssistantSpookSensorEntityDescription, ...] = (
HomeAssistantSpookSensorEntityDescription(
key=Platform.AIR_QUALITY,
translation_key="homeassistant_air_quality",
entity_id="sensor.air_quality",
icon="mdi:air-filter",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.AIR_QUALITY)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.ALARM_CONTROL_PANEL,
translation_key="homeassistant_alarm_control_panel",
entity_id="sensor.alarm_control_panels",
icon="mdi:alarm-panel",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(
hass.states.async_entity_ids(Platform.ALARM_CONTROL_PANEL),
),
),
HomeAssistantSpookSensorEntityDescription(
key="area",
translation_key="homeassistant_area",
entity_id="sensor.areas",
icon="mdi:texture-box",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, ar.EVENT_AREA_REGISTRY_UPDATED},
value_fn=lambda hass: len(list(ar.async_get(hass).async_list_areas())),
),
HomeAssistantSpookSensorEntityDescription(
key=automation.DOMAIN,
translation_key="homeassistant_automation",
entity_id="sensor.automations",
icon="mdi:robot",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={automation.EVENT_AUTOMATION_RELOADED},
value_fn=lambda hass: _count_active_domain_entities(hass, automation.DOMAIN),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.BINARY_SENSOR,
translation_key="homeassistant_binary_sensor",
entity_id="sensor.binary_sensors",
icon="mdi:numeric-10",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.BINARY_SENSOR)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.BUTTON,
translation_key="homeassistant_button",
entity_id="sensor.buttons",
icon="mdi:gesture-tap",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.BUTTON)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.CALENDAR,
translation_key="homeassistant_calendar",
entity_id="sensor.calendars",
icon="mdi:calendar",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.CALENDAR)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.CAMERA,
translation_key="homeassistant_camera",
entity_id="sensor.cameras",
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.CAMERA)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.CLIMATE,
translation_key="homeassistant_climate",
entity_id="sensor.climate",
icon="mdi:thermostat",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.CLIMATE)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.COVER,
translation_key="homeassistant_cover",
entity_id="sensor.covers",
icon="mdi:blinds",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.COVER)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.DATE,
translation_key="homeassistant_date",
entity_id="sensor.dates",
icon="mdi:calendar-month-outline",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.DATE)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.DATETIME,
translation_key="homeassistant_datetime",
entity_id="sensor.datetimes",
icon="mdi:calendar-clock",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.DATETIME)),
),
HomeAssistantSpookSensorEntityDescription(
key="device",
translation_key="homeassistant_device",
entity_id="sensor.devices",
icon="mdi:cellphone",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, dr.EVENT_DEVICE_REGISTRY_UPDATED},
value_fn=lambda hass: len(dr.async_get(hass).devices),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.DEVICE_TRACKER,
translation_key="homeassistant_device_tracker",
entity_id="sensor.device_trackers",
icon="mdi:cellphone-marker",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(
hass.states.async_entity_ids(Platform.DEVICE_TRACKER),
),
),
HomeAssistantSpookSensorEntityDescription(
key="entities",
translation_key="homeassistant_entities",
entity_id="sensor.entities",
icon="mdi:counter",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids()),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.FAN,
translation_key="homeassistant_fan",
entity_id="sensor.fans",
icon="mdi:fan",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.FAN)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.HUMIDIFIER,
translation_key="homeassistant_humidifier",
entity_id="sensor.humidifiers",
icon="mdi:air-humidifier",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.HUMIDIFIER)),
),
HomeAssistantSpookSensorEntityDescription(
key="integration",
translation_key="homeassistant_integration",
entity_id="sensor.integrations",
icon="mdi:package-variant-closed",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED},
value_fn=lambda hass: len(hass.data["entity_platform"]),
),
HomeAssistantSpookSensorEntityDescription(
key="custom_component",
translation_key="homeassistant_custom_component",
entity_id="sensor.custom_integrations",
icon="mdi:package-variant-closed",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED},
value_fn=lambda hass: len(hass.data["custom_components"]),
),
HomeAssistantSpookSensorEntityDescription(
key=input_boolean.DOMAIN,
translation_key="homeassistant_input_boolean",
entity_id="sensor.input_booleans",
icon="mdi:toggle-switch-outline",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_boolean.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=input_button.DOMAIN,
translation_key="homeassistant_input_button",
entity_id="sensor.input_buttons",
icon="mdi:gesture-tap-button",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_button.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=input_datetime.DOMAIN,
translation_key="homeassistant_input_datetime",
entity_id="sensor.input_datetimes",
icon="mdi:clock",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_datetime.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=input_number.DOMAIN,
translation_key="homeassistant_input_number",
entity_id="sensor.input_numbers",
icon="mdi:ray-vertex",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_number.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=input_select.DOMAIN,
translation_key="homeassistant_input_select",
entity_id="sensor.input_selects",
icon="mdi:form-dropdown",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_select.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=input_text.DOMAIN,
translation_key="homeassistant_input_text",
entity_id="sensor.input_texts",
icon="mdi:form-textbox",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(input_text.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.IMAGE,
translation_key="homeassistant_image",
entity_id="sensor.images",
icon="mdi:image",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.IMAGE)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.LIGHT,
translation_key="homeassistant_light",
entity_id="sensor.lights",
icon="mdi:lightbulb",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.LIGHT)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.LOCK,
translation_key="homeassistant_lock",
entity_id="sensor.locks",
icon="mdi:lock",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.LOCK)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.MEDIA_PLAYER,
translation_key="homeassistant_media_player",
entity_id="sensor.media_players",
icon="mdi:record-player",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.MEDIA_PLAYER)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.NUMBER,
translation_key="homeassistant_number",
entity_id="sensor.numbers",
icon="mdi:ray-vertex",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.NUMBER)),
),
HomeAssistantSpookSensorEntityDescription(
key="persistent_notification",
translation_key="homeassistant_persistent_notification",
entity_id="sensor.persistent_notifications",
icon="mdi:bell-ring-outline",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={
"persistent_notifications_updated",
},
value_fn=lambda hass: len(
# pylint: disable-next=protected-access
persistent_notification._async_get_or_create_notifications( # noqa: SLF001
hass,
),
),
),
HomeAssistantSpookSensorEntityDescription(
key=person.DOMAIN,
translation_key="homeassistant_person",
entity_id="sensor.persons",
icon="mdi:account-group",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(person.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.REMOTE,
translation_key="homeassistant_remote",
entity_id="sensor.remotes",
icon="mdi:remote",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.REMOTE)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.SCENE,
translation_key="homeassistant_scene",
entity_id="sensor.scenes",
icon="mdi:palette",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.SCENE)),
),
HomeAssistantSpookSensorEntityDescription(
key=script.DOMAIN,
translation_key="homeassistant_script",
entity_id="sensor.scripts",
icon="mdi:script-text",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: _count_active_domain_entities(hass, script.DOMAIN),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.SELECT,
translation_key="homeassistant_select",
entity_id="sensor.selects",
icon="mdi:format-list-bulleted",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.SELECT)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.SENSOR,
translation_key="homeassistant_sensor",
entity_id="sensor.sensors",
icon="mdi:eye",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.SENSOR)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.SIREN,
translation_key="homeassistant_siren",
entity_id="sensor.sirens",
icon="mdi:bullhorn",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.SIREN)),
),
HomeAssistantSpookSensorEntityDescription(
key=sun.DOMAIN,
translation_key="homeassistant_sun",
entity_id="sensor.suns",
icon="mdi:emoticon-cool",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED},
value_fn=lambda hass: len(hass.states.async_entity_ids(sun.DOMAIN)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.STT,
translation_key="homeassistant_stt",
entity_id="sensor.stt",
icon="mdi:microphone-message",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.STT)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.SWITCH,
translation_key="homeassistant_switch",
entity_id="sensor.switches",
icon="mdi:toggle-switch",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.SWITCH)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.TEXT,
translation_key="homeassistant_text",
entity_id="sensor.texts",
icon="mdi:form-textbox",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.TEXT)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.TIME,
translation_key="homeassistant_time",
entity_id="sensor.times",
icon="mdi:clock-time-eight-outline",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.TIME)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.TODO,
translation_key="homeassistant_todo",
entity_id="sensor.todos",
icon="mdi:clipboard-list",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.TODO)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.TTS,
translation_key="homeassistant_tts",
entity_id="sensor.tts",
icon="mdi:speaker-message",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.TTS)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.VACUUM,
translation_key="homeassistant_vacuum",
entity_id="sensor.vacuums",
icon="mdi:vacuum",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.VACUUM)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.UPDATE,
translation_key="homeassistant_update",
entity_id="sensor.update",
icon="mdi:cellphone-arrow-down",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.UPDATE)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.WATER_HEATER,
translation_key="homeassistant_water_heater",
entity_id="sensor.water_heaters",
icon="mdi:water-boiler",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.WATER_HEATER)),
),
HomeAssistantSpookSensorEntityDescription(
key=Platform.WEATHER,
translation_key="homeassistant_weather",
entity_id="sensor.weather",
icon="mdi:weather-cloudy",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(Platform.WEATHER)),
),
HomeAssistantSpookSensorEntityDescription(
key=zone.DOMAIN,
translation_key="homeassistant_zone",
entity_id="sensor.zones",
icon="mdi:selection-marker",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL,
update_events={EVENT_COMPONENT_LOADED, er.EVENT_ENTITY_REGISTRY_UPDATED},
value_fn=lambda hass: len(hass.states.async_entity_ids(zone.DOMAIN)),
),
)
async def async_setup_entry(
_hass: HomeAssistant,
_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Spook sensor."""
async_add_entities(
HomeAssistantSpookSensorEntity(description) for description in SENSORS
)
class HomeAssistantSpookSensorEntity(HomeAssistantSpookEntity, SensorEntity):
"""Spook sensor providig Home Asistant information."""
entity_description: HomeAssistantSpookSensorEntityDescription
_unsub_debouncer: Callable[[], None] | None = None
async def async_added_to_hass(self) -> None:
"""Register for sensor updates."""
@callback
def _debounced_update(
_now: datetime | None = None,
) -> None:
"""Update state after debounce."""
self._unsub_debouncer = None
self.async_schedule_update_ha_state()
@callback
def _update_state(_: Event) -> None:
"""Update state."""
if self._unsub_debouncer:
self._unsub_debouncer()
self._unsub_debouncer = async_call_later(self.hass, 5, _debounced_update)
for event in self.entity_description.update_events:
self.async_on_remove(self.hass.bus.async_listen(event, _update_state))
self.async_on_remove(
async_listen_once_tracked(
self.hass, EVENT_HOMEASSISTANT_STARTED, _update_state
),
)
async def async_will_remove_from_hass(self) -> None:
"""Clean up debounce timer."""
if self._unsub_debouncer:
self._unsub_debouncer()
self._unsub_debouncer = None
await super().async_will_remove_from_hass()
@property
def native_value(self) -> int | None:
"""Return the sensor value."""
return self.entity_description.value_fn(self.hass)
@@ -0,0 +1 @@
"""Spook - Your homie."""
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an alias to an area."""
domain = DOMAIN
service = "add_alias_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
aliases = area.aliases.copy()
area_registry.async_update(
call.data["area_id"],
aliases=aliases.union(call.data["alias"]),
)
@@ -0,0 +1,43 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an alias to a floor."""
domain = DOMAIN
service = "add_alias_to_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
aliases = floor.aliases.copy()
floor_registry.async_update(
call.data["floor_id"],
aliases=aliases.union(call.data["alias"]),
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add an area to a floor."""
domain = DOMAIN
service = "add_area_to_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
area_registry.async_update(
area_id=area_id,
floor_id=call.data["floor_id"],
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
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,
device_registry as dr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a device to an area."""
domain = DOMAIN
service = "add_device_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
device_registry.async_update_device(
device_id,
area_id=call.data["area_id"],
)
@@ -0,0 +1,45 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a entity to an area."""
domain = DOMAIN
service = "add_entity_to_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id,
area_id=call.data["area_id"],
)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
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,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to an area."""
domain = DOMAIN
service = "add_label_to_area"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
if area_entry := area_registry.async_get_area(area_id):
labels = area_entry.labels.copy()
labels.update(call.data["label_id"])
area_registry.async_update(area_id, labels=labels)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
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,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to a device."""
domain = DOMAIN
service = "add_label_to_device"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
if device_entry := device_registry.async_get(device_id):
labels = device_entry.labels.copy()
labels.update(call.data["label_id"])
device_registry.async_update_device(device_id, labels=labels)
@@ -0,0 +1,46 @@
"""Spook - Your homie."""
from __future__ import annotations
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,
label_registry as lr,
)
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to add a label to an entity."""
domain = DOMAIN
service = "add_label_to_entity"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
for label_id in call.data["label_id"]:
if not label_registry.async_get_label(label_id):
msg = f"Label {label_id} not found"
raise HomeAssistantError(msg)
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
if entity_entry := entity_registry.async_get(entity_id):
labels = entity_entry.labels.copy()
labels.update(call.data["label_id"])
entity_registry.async_update_entity(entity_id, labels=labels)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant area service to create areas on the fly."""
domain = DOMAIN
service = "create_area"
schema = {
vol.Required("name"): cv.string,
vol.Optional("aliases"): [cv.string],
vol.Optional("icon"): cv.icon,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
area_registry.async_create(
name=call.data["name"],
aliases=call.data.get("aliases"),
icon=call.data.get("icon"),
)
@@ -0,0 +1,38 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant floor service to create floors on the fly."""
domain = DOMAIN
service = "create_floor"
schema = {
vol.Required("name"): cv.string,
vol.Optional("aliases"): [cv.string],
vol.Optional("icon"): cv.icon,
vol.Optional("level"): vol.Coerce(int),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
floor_registry.async_create(
name=call.data["name"],
aliases=call.data.get("aliases"),
icon=call.data.get("icon"),
level=call.data.get("level"),
)
@@ -0,0 +1,69 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, label_registry as lr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
SUPPORTED_LABEL_THEME_COLORS = {
"primary",
"accent",
"disabled",
"amber",
"black",
"blue-grey",
"blue",
"brown",
"cyan",
"dark-grey",
"deep-orange",
"deep-purple",
"green",
"grey",
"indigo",
"light-blue",
"light-green",
"light-grey",
"lime",
"orange",
"pink",
"purple",
"red",
"teal",
"white",
"yellow",
}
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to create labels on the fly."""
domain = DOMAIN
service = "create_label"
schema = {
vol.Required("name"): cv.string,
vol.Optional("color"): vol.Any(
cv.color_hex, vol.In(SUPPORTED_LABEL_THEME_COLORS)
),
vol.Optional("description"): cv.string,
vol.Optional("icon"): cv.icon,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
label_registry.async_create(
name=call.data["name"],
color=call.data.get("color"),
description=call.data.get("description"),
icon=call.data.get("icon"),
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.const import ATTR_RESTORED
from homeassistant.helpers import entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to delete all orphaned entities."""
domain = DOMAIN
service = "delete_all_orphaned_entities"
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for state in self.hass.states.async_all():
if not state.attributes.get(ATTR_RESTORED):
continue
entity_registry.async_remove(state.entity_id)
self.hass.states.async_remove(state.entity_id, call.context)
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant area service to delete areas on the fly."""
domain = DOMAIN
service = "delete_area"
schema = {vol.Required("area_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
area_registry.async_delete(call.data["area_id"])
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, floor_registry as fr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant floor service to delete floors on the fly."""
domain = DOMAIN
service = "delete_floor"
schema = {vol.Required("floor_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
floor_registry = fr.async_get(self.hass)
floor_registry.async_delete(call.data["floor_id"])
@@ -0,0 +1,28 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, label_registry as lr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to delete labels on the fly."""
domain = DOMAIN
service = "delete_label"
schema = {vol.Required("label_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
label_registry = lr.async_get(self.hass)
label_registry.async_delete(call.data["label_id"])
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.config_entries import ConfigEntryDisabler
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a config entry."""
domain = DOMAIN
service = "disable_config_entry"
schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for config_entry_id in call.data["config_entry_id"]:
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=ConfigEntryDisabler.USER,
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
from ..device import async_disable_device_and_parent_if_needed
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a device."""
domain = DOMAIN
service = "disable_device"
schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
async_disable_device_and_parent_if_needed(device_registry, device_id)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable an entity."""
domain = DOMAIN
service = "disable_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
disabled_by=er.RegistryEntryDisabler.USER,
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable polling."""
domain = DOMAIN
service = "disable_polling"
schema = {vol.Required("config_entry_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if not (
entry := self.hass.config_entries.async_get_entry(
call.data["config_entry_id"],
)
):
msg = f"Config entry not found: {call.data['config_entry_id']}"
raise HomeAssistantError(msg)
self.hass.config_entries.async_update_entry(
entry,
pref_disable_polling=True,
)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to disable a user."""
domain = DOMAIN
service = "disable_user"
schema = {vol.Required("user_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
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)
if user.system_generated:
message = f"Cannot disable a system-generated user: {user_id}"
raise HomeAssistantError(message)
await self.hass.auth.async_update_user(user, is_active=False)
@@ -0,0 +1,31 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a config entry."""
domain = DOMAIN
service = "enable_config_entry"
schema = {vol.Required("config_entry_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
for config_entry_id in call.data["config_entry_id"]:
await self.hass.config_entries.async_set_disabled_by(
config_entry_id,
disabled_by=None,
)
@@ -0,0 +1,30 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
from ..device import async_enable_device_and_parent
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a device."""
domain = DOMAIN
service = "enable_device"
schema = {vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
async_enable_device_and_parent(device_registry, device_id)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable an entity."""
domain = DOMAIN
service = "enable_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
disabled_by=None,
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable polling."""
domain = DOMAIN
service = "enable_polling"
schema = {vol.Required("config_entry_id"): cv.string}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if not (
entry := self.hass.config_entries.async_get_entry(
call.data["config_entry_id"],
)
):
msg = f"Config entry not found: {call.data['config_entry_id']}"
raise HomeAssistantError(msg)
self.hass.config_entries.async_update_entry(
entry,
pref_disable_polling=False,
)
@@ -0,0 +1,36 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to enable a user."""
domain = DOMAIN
service = "enable_user"
schema = {vol.Required("user_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
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)
if user.system_generated:
message = f"Cannot enable a system-generated user: {user_id}"
raise HomeAssistantError(message)
await self.hass.auth.async_update_user(user, is_active=True)
@@ -0,0 +1,33 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to hide an entity."""
domain = DOMAIN
service = "hide_entity"
admin = True
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
hidden_by=er.RegistryEntryHider.USER,
)
@@ -0,0 +1,76 @@
"""Spook - Your homie."""
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.config_entries import DISCOVERY_SOURCES, SOURCE_IGNORE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.translation import async_get_translations
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to ignore all discovered devices."""
domain = DOMAIN
service = "ignore_all_discovered"
schema = {vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
flows_to_ignore = [
flow
for flow in self.hass.config_entries.flow.async_progress()
if (
"context" in flow
and "source" in flow["context"]
and flow["context"]["source"] in DISCOVERY_SOURCES
and (
"domain" not in call.data or flow["handler"] in call.data["domain"]
)
)
]
translations = await async_get_translations(
self.hass,
"en",
"config_flow",
integrations=(flow["handler"] for flow in flows_to_ignore),
config_flow=True,
)
tasks = []
for flow in flows_to_ignore:
title = "Ignored by Spook"
if flow_title := translations.get(
f"component.{flow['handler']}.config.flow_title",
):
title = flow_title.format(**flow["context"]["title_placeholders"])
elif (
"title_placeholders" in flow["context"]
and "name" in flow["context"]["title_placeholders"]
):
title = flow["context"]["title_placeholders"]["name"]
tasks.append(
self.hass.config_entries.flow.async_init(
flow["handler"],
context={"source": SOURCE_IGNORE},
data={
"unique_id": flow["context"]["unique_id"],
"title": f"{title} 👻",
},
),
)
if tasks:
await asyncio.gather(*tasks)
@@ -0,0 +1,47 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import create_engine, text
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.components.recorder import (
get_instance,
)
from homeassistant.core import ServiceResponse, SupportsResponse
from ....services import AbstractSpookService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookService):
"""Home Assistant Core integration service to list all orphaned database entities."""
domain = DOMAIN
service = "list_orphaned_database_entities"
supports_response = SupportsResponse.ONLY
async def async_handle_service(self, call: ServiceCall) -> ServiceResponse:
"""Handle the service call."""
query = text(
"""
SELECT DISTINCT(entity_id) FROM states_meta
"""
)
db_url = get_instance(self.hass).db_url
engine = create_engine(db_url)
with engine.connect() as conn:
response = conn.execute(query)
db_list = [e[0] for e in response]
states_list = self.hass.states.async_entity_ids()
compared_list = set(db_list).difference(states_list)
if call.return_response:
return {
"count": len(compared_list),
"entities": list(compared_list),
}
return None
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an alias to an area."""
domain = DOMAIN
service = "remove_alias_from_area"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
aliases = area.aliases.copy()
area_registry.async_update(
call.data["area_id"],
aliases=aliases.difference(call.data["alias"]),
)
@@ -0,0 +1,40 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an alias from a floor."""
domain = DOMAIN
service = "remove_alias_from_floor"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("alias"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
aliases = floor.aliases.copy()
floor_registry.async_update(
call.data["floor_id"],
aliases=aliases.difference(call.data["alias"]),
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an area from a floor."""
domain = DOMAIN
service = "remove_area_from_floor"
schema = {
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
area_registry.async_update(
area_id,
floor_id=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a device from an area."""
domain = DOMAIN
service = "remove_device_from_area"
schema = {
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
device_registry.async_update_device(
device_id,
area_id=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove an entity from an area."""
domain = DOMAIN
service = "remove_entity_from_area"
schema = {
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id,
area_id=None,
)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import area_registry as ar, config_validation as cv
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from an area."""
domain = DOMAIN
service = "remove_label_from_area"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("area_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
area_registry = ar.async_get(self.hass)
for area_id in call.data["area_id"]:
if area_entry := area_registry.async_get_area(area_id):
labels = area_entry.labels.copy()
labels.difference_update(call.data["label_id"])
area_registry.async_update(area_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, device_registry as dr
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from a device."""
domain = DOMAIN
service = "remove_label_from_device"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("device_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
device_registry = dr.async_get(self.hass)
for device_id in call.data["device_id"]:
if device_entry := device_registry.async_get(device_id):
labels = device_entry.labels.copy()
labels.difference_update(call.data["label_id"])
device_registry.async_update_device(device_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to remove a label from an entity."""
domain = DOMAIN
service = "remove_label_from_entity"
schema = {
vol.Required("label_id"): vol.All(cv.ensure_list, [cv.string]),
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
if entity_entry := entity_registry.async_get(entity_id):
labels = entity_entry.labels.copy()
labels.difference_update(call.data["label_id"])
entity_registry.async_update_entity(entity_id, labels=labels)
@@ -0,0 +1,35 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to rename an entity."""
domain = DOMAIN
service = "rename_entity"
schema = {
vol.Required("name"): cv.string,
vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
name=call.data["name"],
)
@@ -0,0 +1,50 @@
"""Spook - Your homie."""
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
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 ....services import AbstractSpookAdminService, ReplaceExistingService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService, ReplaceExistingService):
"""Home Assistant service to restart Home Assistant.
It overrides the built-in restart service to add a force option.
"""
domain = DOMAIN
service = "restart"
schema = {
vol.Optional("safe_mode", default=False): cv.boolean,
vol.Optional("force", default=False): cv.boolean,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
if call.data["force"]:
LOGGER.warning("!! Forcing an Home Assistant restart !!")
self.hass.data["homeassistant_stop"] = asyncio.create_task(
self.hass.async_stop(RESTART_EXIT_CODE),
)
return
if not self.overriden_service:
msg = "Spook encountered an error while restarting Home Assistant."
raise HomeAssistantError(
msg,
)
self.hass.async_run_hass_job(self.overriden_service.job, call)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to set the aliases of an area."""
domain = DOMAIN
service = "set_area_aliases"
schema = {
vol.Required("area_id"): cv.string,
vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
area_registry.async_update(
call.data["area_id"],
aliases=set(call.data["aliases"]),
)
@@ -0,0 +1,39 @@
"""Spook - Your homie."""
from __future__ import annotations
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 ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant service to set the aliases of a floor."""
domain = DOMAIN
service = "set_floor_aliases"
schema = {
vol.Required("floor_id"): cv.string,
vol.Required("aliases"): vol.All(cv.ensure_list, [cv.string]),
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""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)
floor_registry.async_update(
call.data["floor_id"],
aliases=set(call.data["aliases"]),
)
@@ -0,0 +1,32 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to unhide an entity."""
domain = DOMAIN
service = "unhide_entity"
schema = {vol.Required("entity_id"): vol.All(cv.ensure_list, [cv.string])}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
for entity_id in call.data["entity_id"]:
entity_registry.async_update_entity(
entity_id=entity_id,
hidden_by=None,
)
@@ -0,0 +1,34 @@
"""Spook - Your homie."""
from __future__ import annotations
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.homeassistant import DOMAIN
from homeassistant.helpers import config_validation as cv, entity_registry as er
from ....services import AbstractSpookAdminService
if TYPE_CHECKING:
from homeassistant.core import ServiceCall
class SpookService(AbstractSpookAdminService):
"""Home Assistant Core integration service to update an entity's ID."""
domain = DOMAIN
service = "update_entity_id"
schema = {
vol.Required("entity_id"): cv.entity_id,
vol.Required("new_entity_id"): cv.entity_id,
}
async def async_handle_service(self, call: ServiceCall) -> None:
"""Handle the service call."""
entity_registry = er.async_get(self.hass)
entity_registry.async_update_entity(
entity_id=call.data["entity_id"],
new_entity_id=call.data["new_entity_id"],
)