Updated apps
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""Spook - Your homie."""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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."""
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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"),
|
||||
)
|
||||
+30
@@ -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)
|
||||
+47
@@ -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"],
|
||||
)
|
||||
Reference in New Issue
Block a user