mirror of
https://github.com/home-assistant/core.git
synced 2026-06-30 03:06:10 +01:00
251 lines
7.2 KiB
Python
251 lines
7.2 KiB
Python
"""Support for device trackers which integrates with other components."""
|
|
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components import zone
|
|
from homeassistant.components.device_tracker import (
|
|
DOMAIN as DEVICE_TRACKER_DOMAIN,
|
|
ENTITY_ID_FORMAT,
|
|
TrackerEntity,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.entity_platform import (
|
|
AddConfigEntryEntitiesCallback,
|
|
AddEntitiesCallback,
|
|
)
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
|
|
from . import TriggerUpdateCoordinator, validators as template_validators
|
|
from .entity import AbstractTemplateEntity
|
|
from .helpers import (
|
|
async_setup_template_entry,
|
|
async_setup_template_platform,
|
|
async_setup_template_preview,
|
|
)
|
|
from .schemas import (
|
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
|
make_template_entity_common_modern_schema,
|
|
)
|
|
from .template_entity import TemplateEntity
|
|
from .trigger_entity import TriggerEntity
|
|
|
|
DEFAULT_NAME = "Template Device Tracker"
|
|
|
|
CONF_IN_ZONES = "in_zones"
|
|
CONF_LOCATION_ACCURACY = "location_accuracy"
|
|
|
|
|
|
def _validate_in_zones_or_lat_and_lon(obj: dict) -> dict:
|
|
if CONF_IN_ZONES not in obj:
|
|
if CONF_LATITUDE not in obj or CONF_LONGITUDE not in obj:
|
|
raise vol.Invalid(
|
|
f"Either '{CONF_IN_ZONES}' or both '{CONF_LATITUDE}' and '{CONF_LONGITUDE}' must be specified"
|
|
)
|
|
elif (CONF_LATITUDE in obj and CONF_LONGITUDE not in obj) or (
|
|
CONF_LATITUDE not in obj and CONF_LONGITUDE in obj
|
|
):
|
|
raise vol.Invalid(
|
|
f"Both '{CONF_LATITUDE}' and '{CONF_LONGITUDE}' must be specified"
|
|
)
|
|
|
|
return obj
|
|
|
|
|
|
def validate_in_zones(
|
|
entity: AbstractTemplateTracker,
|
|
) -> Callable[[Any], list[str] | None]:
|
|
"""Convert the result to a list of entity_ids.
|
|
|
|
This ensures the result is a list of zone entity_ids.
|
|
All other values that are not lists will result in None.
|
|
"""
|
|
|
|
def convert(result: Any) -> list[str] | None:
|
|
if template_validators.check_result_for_none(result):
|
|
return None
|
|
|
|
if not isinstance(result, list):
|
|
template_validators.log_validation_result_error(
|
|
entity,
|
|
CONF_IN_ZONES,
|
|
result,
|
|
"expected a list of zone entity_ids",
|
|
)
|
|
return None
|
|
|
|
zone_entity_ids = []
|
|
failed = []
|
|
for v in result:
|
|
try:
|
|
zone_entity_ids.append(
|
|
vol.All(cv.entity_id, cv.entity_domain(zone.DOMAIN))(v)
|
|
)
|
|
except vol.Invalid:
|
|
failed.append(v)
|
|
|
|
if failed:
|
|
template_validators.log_validation_result_error(
|
|
entity,
|
|
CONF_IN_ZONES,
|
|
failed,
|
|
"expected a list of zone entity_ids",
|
|
)
|
|
|
|
return zone_entity_ids
|
|
|
|
return convert
|
|
|
|
|
|
TRACKER_COMMON_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Optional(CONF_IN_ZONES): cv.template,
|
|
vol.Optional(CONF_LATITUDE): cv.template,
|
|
vol.Optional(CONF_LOCATION_ACCURACY): cv.template,
|
|
vol.Optional(CONF_LONGITUDE): cv.template,
|
|
}
|
|
)
|
|
|
|
|
|
TRACKER_YAML_SCHEMA = vol.All(
|
|
_validate_in_zones_or_lat_and_lon,
|
|
TRACKER_COMMON_SCHEMA.extend(
|
|
make_template_entity_common_modern_schema(
|
|
DEVICE_TRACKER_DOMAIN, DEFAULT_NAME
|
|
).schema
|
|
),
|
|
)
|
|
|
|
TRACKER_CONFIG_ENTRY_SCHEMA = vol.All(
|
|
_validate_in_zones_or_lat_and_lon,
|
|
TRACKER_COMMON_SCHEMA.extend(TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema),
|
|
)
|
|
|
|
|
|
async def async_setup_platform(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
async_add_entities: AddEntitiesCallback,
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
) -> None:
|
|
"""Set up the template device trackers."""
|
|
await async_setup_template_platform(
|
|
hass,
|
|
DEVICE_TRACKER_DOMAIN,
|
|
config,
|
|
StateTrackerEntity,
|
|
TriggerTrackerEntity,
|
|
async_add_entities,
|
|
discovery_info,
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Initialize config entry."""
|
|
await async_setup_template_entry(
|
|
hass,
|
|
config_entry,
|
|
async_add_entities,
|
|
StateTrackerEntity,
|
|
TRACKER_CONFIG_ENTRY_SCHEMA,
|
|
)
|
|
|
|
|
|
@callback
|
|
def async_create_preview_tracker(
|
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
|
) -> StateTrackerEntity:
|
|
"""Create a preview device tracker."""
|
|
return async_setup_template_preview(
|
|
hass,
|
|
name,
|
|
config,
|
|
StateTrackerEntity,
|
|
TRACKER_CONFIG_ENTRY_SCHEMA,
|
|
)
|
|
|
|
|
|
class AbstractTemplateTracker(AbstractTemplateEntity, TrackerEntity):
|
|
"""Representation of a template device tracker features."""
|
|
|
|
_entity_id_format = ENTITY_ID_FORMAT
|
|
|
|
# The super init is not called because TemplateEntity
|
|
# and TriggerEntity will call
|
|
# AbstractTemplateEntity.__init__. This ensures that
|
|
# the __init__ on AbstractTemplateEntity is not
|
|
# called twice.
|
|
def __init__(self) -> None: # pylint: disable=super-init-not-called
|
|
"""Initialize the features."""
|
|
|
|
self.setup_template(
|
|
CONF_IN_ZONES,
|
|
"_attr_in_zones",
|
|
validate_in_zones(self),
|
|
)
|
|
self.setup_template(
|
|
CONF_LATITUDE,
|
|
"_attr_latitude",
|
|
template_validators.number(self, CONF_LATITUDE, -90.0, 90.0),
|
|
)
|
|
self.setup_template(
|
|
CONF_LONGITUDE,
|
|
"_attr_longitude",
|
|
template_validators.number(self, CONF_LONGITUDE, -180.0, 180.0),
|
|
)
|
|
self.setup_template(
|
|
CONF_LOCATION_ACCURACY,
|
|
"_attr_location_accuracy",
|
|
on_update=self._update_location_accuracy,
|
|
none_on_template_error=False,
|
|
)
|
|
|
|
self._location_accuracy_validator = template_validators.number(
|
|
self, CONF_LOCATION_ACCURACY, 0.0
|
|
)
|
|
|
|
def _update_location_accuracy(self, value: float | None) -> None:
|
|
"""Update the location accuracy."""
|
|
self._attr_location_accuracy = self._location_accuracy_validator(value) or 0.0
|
|
|
|
|
|
class StateTrackerEntity(TemplateEntity, AbstractTemplateTracker):
|
|
"""Representation of a Template device tracker."""
|
|
|
|
_attr_should_poll = False
|
|
|
|
def __init__(
|
|
self,
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
unique_id: str | None,
|
|
) -> None:
|
|
"""Initialize the Template device tracker."""
|
|
TemplateEntity.__init__(self, hass, config, unique_id)
|
|
AbstractTemplateTracker.__init__(self)
|
|
|
|
|
|
class TriggerTrackerEntity(TriggerEntity, AbstractTemplateTracker):
|
|
"""Tracker entity based on trigger data."""
|
|
|
|
domain = DEVICE_TRACKER_DOMAIN
|
|
|
|
def __init__(
|
|
self,
|
|
hass: HomeAssistant,
|
|
coordinator: TriggerUpdateCoordinator,
|
|
config: ConfigType,
|
|
) -> None:
|
|
"""Initialize the entity."""
|
|
TriggerEntity.__init__(self, hass, coordinator, config)
|
|
AbstractTemplateTracker.__init__(self)
|