2016-03-09 22:49:54 +00:00
|
|
|
"""An abstract class for entities."""
|
2019-11-19 19:42:09 +00:00
|
|
|
from abc import ABC
|
2019-09-29 17:07:49 +00:00
|
|
|
import asyncio
|
|
|
|
from datetime import datetime, timedelta
|
2016-11-16 05:06:50 +00:00
|
|
|
import functools as ft
|
2019-12-09 15:42:10 +00:00
|
|
|
import logging
|
2016-11-04 04:58:25 +00:00
|
|
|
from timeit import default_timer as timer
|
2019-09-24 21:20:04 +00:00
|
|
|
from typing import Any, Dict, Iterable, List, Optional, Union
|
2016-07-28 03:33:49 +00:00
|
|
|
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant.config import DATA_CUSTOMIZE
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_ASSUMED_STATE,
|
2019-12-09 15:42:10 +00:00
|
|
|
ATTR_DEVICE_CLASS,
|
|
|
|
ATTR_ENTITY_PICTURE,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_FRIENDLY_NAME,
|
|
|
|
ATTR_HIDDEN,
|
|
|
|
ATTR_ICON,
|
2019-12-09 15:42:10 +00:00
|
|
|
ATTR_SUPPORTED_FEATURES,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_UNIT_OF_MEASUREMENT,
|
|
|
|
DEVICE_DEFAULT_NAME,
|
|
|
|
STATE_OFF,
|
|
|
|
STATE_ON,
|
|
|
|
STATE_UNAVAILABLE,
|
|
|
|
STATE_UNKNOWN,
|
|
|
|
TEMP_CELSIUS,
|
|
|
|
TEMP_FAHRENHEIT,
|
|
|
|
)
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback
|
|
|
|
from homeassistant.exceptions import NoEntitySpecifiedError
|
2019-09-29 17:07:49 +00:00
|
|
|
from homeassistant.helpers.entity_platform import EntityPlatform
|
2019-07-21 16:59:02 +00:00
|
|
|
from homeassistant.helpers.entity_registry import (
|
2019-07-31 19:25:30 +00:00
|
|
|
EVENT_ENTITY_REGISTRY_UPDATED,
|
|
|
|
RegistryEntry,
|
|
|
|
)
|
2019-12-09 15:42:10 +00:00
|
|
|
from homeassistant.util import dt as dt_util, ensure_unique_string, slugify
|
2018-03-11 17:01:12 +00:00
|
|
|
from homeassistant.util.async_ import run_callback_threadsafe
|
2019-07-21 16:59:02 +00:00
|
|
|
|
2019-08-15 15:53:25 +00:00
|
|
|
# mypy: allow-untyped-defs, no-check-untyped-defs, no-warn-return-any
|
2019-07-21 16:59:02 +00:00
|
|
|
|
2016-06-22 16:13:18 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2017-03-14 16:26:55 +00:00
|
|
|
SLOW_UPDATE_WARNING = 10
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-01-24 06:37:15 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def generate_entity_id(
|
|
|
|
entity_id_format: str,
|
|
|
|
name: Optional[str],
|
|
|
|
current_ids: Optional[List[str]] = None,
|
|
|
|
hass: Optional[HomeAssistant] = None,
|
|
|
|
) -> str:
|
2016-03-25 19:35:38 +00:00
|
|
|
"""Generate a unique entity ID based on given entity IDs or used IDs."""
|
2016-01-24 07:00:46 +00:00
|
|
|
if current_ids is None:
|
|
|
|
if hass is None:
|
2016-06-22 16:13:18 +00:00
|
|
|
raise ValueError("Missing required parameter currentids or hass")
|
2019-02-27 21:10:40 +00:00
|
|
|
return run_callback_threadsafe(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.loop,
|
|
|
|
async_generate_entity_id,
|
|
|
|
entity_id_format,
|
|
|
|
name,
|
|
|
|
current_ids,
|
|
|
|
hass,
|
2019-02-27 21:10:40 +00:00
|
|
|
).result()
|
2016-01-24 07:00:46 +00:00
|
|
|
|
2019-07-21 16:59:02 +00:00
|
|
|
name = (slugify(name or "") or slugify(DEVICE_DEFAULT_NAME)).lower()
|
2016-01-24 07:00:46 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return ensure_unique_string(entity_id_format.format(name), current_ids)
|
2016-10-01 08:22:13 +00:00
|
|
|
|
|
|
|
|
2017-11-17 05:03:05 +00:00
|
|
|
@callback
|
2019-07-31 19:25:30 +00:00
|
|
|
def async_generate_entity_id(
|
|
|
|
entity_id_format: str,
|
|
|
|
name: Optional[str],
|
|
|
|
current_ids: Optional[Iterable[str]] = None,
|
|
|
|
hass: Optional[HomeAssistant] = None,
|
|
|
|
) -> str:
|
2016-10-01 08:22:13 +00:00
|
|
|
"""Generate a unique entity ID based on given entity IDs or used IDs."""
|
2016-10-16 16:35:46 +00:00
|
|
|
if current_ids is None:
|
|
|
|
if hass is None:
|
|
|
|
raise ValueError("Missing required parameter currentids or hass")
|
|
|
|
|
|
|
|
current_ids = hass.states.async_entity_ids()
|
2016-10-01 08:22:13 +00:00
|
|
|
name = (name or DEVICE_DEFAULT_NAME).lower()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
return ensure_unique_string(entity_id_format.format(slugify(name)), current_ids)
|
2016-01-24 07:00:46 +00:00
|
|
|
|
|
|
|
|
2019-11-19 19:42:09 +00:00
|
|
|
class Entity(ABC):
|
2016-03-25 19:35:38 +00:00
|
|
|
"""An abstract class for Home Assistant entities."""
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-04-15 02:57:32 +00:00
|
|
|
# SAFE TO OVERWRITE
|
2016-03-25 19:35:38 +00:00
|
|
|
# The properties and methods here are safe to overwrite when inheriting
|
|
|
|
# this class. These may be used to customize the behavior of the entity.
|
2016-10-01 04:38:39 +00:00
|
|
|
entity_id = None # type: str
|
|
|
|
|
2018-01-23 06:54:41 +00:00
|
|
|
# Owning hass instance. Will be set by EntityPlatform
|
2019-09-04 03:36:04 +00:00
|
|
|
hass: Optional[HomeAssistant] = None
|
2016-10-01 04:38:39 +00:00
|
|
|
|
2018-01-23 06:54:41 +00:00
|
|
|
# Owning platform instance. Will be set by EntityPlatform
|
2019-09-29 17:07:49 +00:00
|
|
|
platform: Optional[EntityPlatform] = None
|
2018-01-23 06:54:41 +00:00
|
|
|
|
2016-12-17 21:00:08 +00:00
|
|
|
# If we reported if this entity was slow
|
|
|
|
_slow_reported = False
|
|
|
|
|
2019-08-22 21:12:24 +00:00
|
|
|
# If we reported this entity is updated while disabled
|
|
|
|
_disabled_reported = False
|
|
|
|
|
2017-10-19 08:56:25 +00:00
|
|
|
# Protect for multiple updates
|
|
|
|
_update_staged = False
|
|
|
|
|
2018-01-29 22:37:19 +00:00
|
|
|
# Process updates in parallel
|
2019-09-29 17:07:49 +00:00
|
|
|
parallel_updates: Optional[asyncio.Semaphore] = None
|
2017-03-14 16:26:55 +00:00
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
# Entry in the entity registry
|
2019-09-04 03:36:04 +00:00
|
|
|
registry_entry: Optional[RegistryEntry] = None
|
2018-02-11 17:16:01 +00:00
|
|
|
|
2018-07-24 12:12:53 +00:00
|
|
|
# Hold list for functions to call on remove.
|
2019-09-04 03:36:04 +00:00
|
|
|
_on_remove: Optional[List[CALLBACK_TYPE]] = None
|
2018-07-24 12:12:53 +00:00
|
|
|
|
2018-08-20 15:39:53 +00:00
|
|
|
# Context
|
2019-09-29 17:07:49 +00:00
|
|
|
_context: Optional[Context] = None
|
|
|
|
_context_set: Optional[datetime] = None
|
2018-08-20 15:39:53 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def should_poll(self) -> bool:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return True if entity has to be polled for state.
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
False if entity pushes its state to HA.
|
|
|
|
"""
|
|
|
|
return True
|
|
|
|
|
|
|
|
@property
|
2019-07-21 16:59:02 +00:00
|
|
|
def unique_id(self) -> Optional[str]:
|
2018-03-03 18:23:55 +00:00
|
|
|
"""Return a unique ID."""
|
2018-01-30 09:39:39 +00:00
|
|
|
return None
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def name(self) -> Optional[str]:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the name of the entity."""
|
2016-02-14 08:21:20 +00:00
|
|
|
return None
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def state(self) -> Union[None, str, int, float]:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the state of the entity."""
|
2015-09-10 06:37:15 +00:00
|
|
|
return STATE_UNKNOWN
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-12-02 19:15:50 +00:00
|
|
|
@property
|
|
|
|
def capability_attributes(self) -> Optional[Dict[str, Any]]:
|
|
|
|
"""Return the capability attributes.
|
|
|
|
|
|
|
|
Attributes that explain the capabilities of an entity.
|
|
|
|
|
|
|
|
Implemented by component base class. Convention for attribute names
|
|
|
|
is lowercase snake_case.
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def state_attributes(self) -> Optional[Dict[str, Any]]:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the state attributes.
|
2016-02-07 06:28:29 +00:00
|
|
|
|
2019-10-07 15:16:26 +00:00
|
|
|
Implemented by component base class. Convention for attribute names
|
|
|
|
is lowercase snake_case.
|
2016-02-07 06:28:29 +00:00
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def device_state_attributes(self) -> Optional[Dict[str, Any]]:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return device specific state attributes.
|
2016-02-07 06:28:29 +00:00
|
|
|
|
2019-10-07 15:16:26 +00:00
|
|
|
Implemented by platform classes. Convention for attribute names
|
|
|
|
is lowercase snake_case.
|
2016-02-07 06:28:29 +00:00
|
|
|
"""
|
2015-09-10 06:37:15 +00:00
|
|
|
return None
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2018-08-22 08:46:37 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def device_info(self) -> Optional[Dict[str, Any]]:
|
2018-08-22 08:46:37 +00:00
|
|
|
"""Return device specific attributes.
|
|
|
|
|
|
|
|
Implemented by platform classes.
|
|
|
|
"""
|
|
|
|
return None
|
|
|
|
|
2017-02-11 04:46:15 +00:00
|
|
|
@property
|
2019-07-21 16:59:02 +00:00
|
|
|
def device_class(self) -> Optional[str]:
|
2017-02-11 04:46:15 +00:00
|
|
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
|
|
|
return None
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def unit_of_measurement(self) -> Optional[str]:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the unit of measurement of this entity, if any."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return None
|
|
|
|
|
2015-11-03 08:20:48 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def icon(self) -> Optional[str]:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the icon to use in the frontend, if any."""
|
2015-11-03 08:20:48 +00:00
|
|
|
return None
|
|
|
|
|
2016-02-24 06:41:24 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def entity_picture(self) -> Optional[str]:
|
2016-02-24 06:41:24 +00:00
|
|
|
"""Return the entity picture to use in the frontend, if any."""
|
|
|
|
return None
|
|
|
|
|
2015-04-23 13:41:41 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def hidden(self) -> bool:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return True if the entity should be hidden from UIs."""
|
2015-09-10 06:37:15 +00:00
|
|
|
return False
|
2015-04-23 13:41:41 +00:00
|
|
|
|
2016-01-31 02:03:51 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def available(self) -> bool:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return True if entity is available."""
|
|
|
|
return True
|
|
|
|
|
2016-02-14 07:42:11 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def assumed_state(self) -> bool:
|
2016-03-25 19:35:38 +00:00
|
|
|
"""Return True if unable to access real state of the entity."""
|
2018-02-08 11:16:51 +00:00
|
|
|
return False
|
2016-02-14 07:42:11 +00:00
|
|
|
|
2016-06-26 07:33:23 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def force_update(self) -> bool:
|
2016-06-26 07:33:23 +00:00
|
|
|
"""Return True if state updates should be forced.
|
|
|
|
|
|
|
|
If True, a state change will be triggered anytime the state property is
|
|
|
|
updated, not just when the value changes.
|
|
|
|
"""
|
|
|
|
return False
|
|
|
|
|
2017-02-08 04:42:45 +00:00
|
|
|
@property
|
2019-07-21 16:59:02 +00:00
|
|
|
def supported_features(self) -> Optional[int]:
|
2017-02-08 04:42:45 +00:00
|
|
|
"""Flag supported features."""
|
|
|
|
return None
|
|
|
|
|
2018-08-20 15:39:53 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def context_recent_time(self) -> timedelta:
|
2018-08-20 15:39:53 +00:00
|
|
|
"""Time that a context is considered recent."""
|
|
|
|
return timedelta(seconds=5)
|
|
|
|
|
2019-08-16 23:17:16 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def entity_registry_enabled_default(self) -> bool:
|
2019-08-16 23:17:16 +00:00
|
|
|
"""Return if the entity should be enabled when first added to the entity registry."""
|
|
|
|
return True
|
|
|
|
|
2015-04-15 02:57:32 +00:00
|
|
|
# DO NOT OVERWRITE
|
|
|
|
# These properties and methods are either managed by Home Assistant or they
|
|
|
|
# are used to perform a very specific function. Overwriting these may
|
|
|
|
# produce undesirable effects in the entity's operation.
|
|
|
|
|
2019-08-28 20:38:20 +00:00
|
|
|
@property
|
2019-09-24 21:20:04 +00:00
|
|
|
def enabled(self) -> bool:
|
2019-09-24 21:21:00 +00:00
|
|
|
"""Return if the entity is enabled in the entity registry.
|
|
|
|
|
|
|
|
If an entity is not part of the registry, it cannot be disabled
|
|
|
|
and will therefore always be enabled.
|
|
|
|
"""
|
2019-08-28 20:38:20 +00:00
|
|
|
return self.registry_entry is None or not self.registry_entry.disabled
|
|
|
|
|
2018-08-20 15:39:53 +00:00
|
|
|
@callback
|
2019-09-24 21:20:04 +00:00
|
|
|
def async_set_context(self, context: Context) -> None:
|
2018-08-20 15:39:53 +00:00
|
|
|
"""Set the context the entity currently operates under."""
|
|
|
|
self._context = context
|
|
|
|
self._context_set = dt_util.utcnow()
|
|
|
|
|
2018-10-01 06:56:50 +00:00
|
|
|
async def async_update_ha_state(self, force_refresh=False):
|
2016-09-30 19:57:24 +00:00
|
|
|
"""Update Home Assistant with current state of entity.
|
|
|
|
|
|
|
|
If force_refresh == True will update entity before setting state.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
"""
|
2015-03-22 01:49:30 +00:00
|
|
|
if self.hass is None:
|
2019-08-23 16:53:33 +00:00
|
|
|
raise RuntimeError(f"Attribute hass is None for {self}")
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
if self.entity_id is None:
|
|
|
|
raise NoEntitySpecifiedError(
|
2019-08-23 16:53:33 +00:00
|
|
|
f"No entity id specified for entity {self.name}"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2017-03-14 16:26:55 +00:00
|
|
|
# update entity data
|
2015-03-22 01:49:30 +00:00
|
|
|
if force_refresh:
|
2017-03-14 16:26:55 +00:00
|
|
|
try:
|
2018-10-01 06:56:50 +00:00
|
|
|
await self.async_device_update()
|
2017-03-14 16:26:55 +00:00
|
|
|
except Exception: # pylint: disable=broad-except
|
2017-05-02 16:18:47 +00:00
|
|
|
_LOGGER.exception("Update for %s fails", self.entity_id)
|
2017-03-14 16:26:55 +00:00
|
|
|
return
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-03-09 17:52:22 +00:00
|
|
|
self._async_write_ha_state()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_write_ha_state(self):
|
|
|
|
"""Write the state to the state machine."""
|
|
|
|
if self.hass is None:
|
2019-08-23 16:53:33 +00:00
|
|
|
raise RuntimeError(f"Attribute hass is None for {self}")
|
2019-03-09 17:52:22 +00:00
|
|
|
|
|
|
|
if self.entity_id is None:
|
|
|
|
raise NoEntitySpecifiedError(
|
2019-08-23 16:53:33 +00:00
|
|
|
f"No entity id specified for entity {self.name}"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-03-09 17:52:22 +00:00
|
|
|
|
|
|
|
self._async_write_ha_state()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def _async_write_ha_state(self):
|
|
|
|
"""Write the state to the state machine."""
|
2019-08-22 21:12:24 +00:00
|
|
|
if self.registry_entry and self.registry_entry.disabled_by:
|
|
|
|
if not self._disabled_reported:
|
|
|
|
self._disabled_reported = True
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Entity %s is incorrectly being triggered for updates while it is disabled. This is a bug in the %s integration.",
|
|
|
|
self.entity_id,
|
|
|
|
self.platform.platform_name,
|
|
|
|
)
|
|
|
|
return
|
|
|
|
|
2016-11-04 04:58:25 +00:00
|
|
|
start = timer()
|
|
|
|
|
2019-12-31 13:29:43 +00:00
|
|
|
attr = self.capability_attributes
|
|
|
|
attr = dict(attr) if attr else {}
|
|
|
|
|
2017-02-10 16:59:58 +00:00
|
|
|
if not self.available:
|
|
|
|
state = STATE_UNAVAILABLE
|
2016-11-04 04:58:25 +00:00
|
|
|
else:
|
2017-02-10 16:59:58 +00:00
|
|
|
state = self.state
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2017-02-10 16:59:58 +00:00
|
|
|
if state is None:
|
|
|
|
state = STATE_UNKNOWN
|
|
|
|
else:
|
|
|
|
state = str(state)
|
2016-02-07 06:28:29 +00:00
|
|
|
|
2019-04-17 16:52:08 +00:00
|
|
|
attr.update(self.state_attributes or {})
|
|
|
|
attr.update(self.device_state_attributes or {})
|
2016-02-07 06:28:29 +00:00
|
|
|
|
2018-02-08 11:16:51 +00:00
|
|
|
unit_of_measurement = self.unit_of_measurement
|
|
|
|
if unit_of_measurement is not None:
|
|
|
|
attr[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
|
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
entry = self.registry_entry
|
|
|
|
# pylint: disable=consider-using-ternary
|
|
|
|
name = (entry and entry.name) or self.name
|
2018-02-08 11:16:51 +00:00
|
|
|
if name is not None:
|
|
|
|
attr[ATTR_FRIENDLY_NAME] = name
|
|
|
|
|
|
|
|
icon = self.icon
|
|
|
|
if icon is not None:
|
|
|
|
attr[ATTR_ICON] = icon
|
|
|
|
|
|
|
|
entity_picture = self.entity_picture
|
|
|
|
if entity_picture is not None:
|
|
|
|
attr[ATTR_ENTITY_PICTURE] = entity_picture
|
2016-02-01 01:11:16 +00:00
|
|
|
|
2018-02-08 11:16:51 +00:00
|
|
|
hidden = self.hidden
|
|
|
|
if hidden:
|
|
|
|
attr[ATTR_HIDDEN] = hidden
|
|
|
|
|
|
|
|
assumed_state = self.assumed_state
|
|
|
|
if assumed_state:
|
|
|
|
attr[ATTR_ASSUMED_STATE] = assumed_state
|
|
|
|
|
|
|
|
supported_features = self.supported_features
|
|
|
|
if supported_features is not None:
|
|
|
|
attr[ATTR_SUPPORTED_FEATURES] = supported_features
|
|
|
|
|
|
|
|
device_class = self.device_class
|
|
|
|
if device_class is not None:
|
|
|
|
attr[ATTR_DEVICE_CLASS] = str(device_class)
|
2016-02-14 07:42:11 +00:00
|
|
|
|
2016-11-04 04:58:25 +00:00
|
|
|
end = timer()
|
|
|
|
|
2018-02-08 11:16:51 +00:00
|
|
|
if end - start > 0.4 and not self._slow_reported:
|
2016-12-17 21:00:08 +00:00
|
|
|
self._slow_reported = True
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.warning(
|
|
|
|
"Updating state for %s (%s) took %.3f seconds. "
|
|
|
|
"Please report platform to the developers at "
|
|
|
|
"https://goo.gl/Nvioub",
|
|
|
|
self.entity_id,
|
|
|
|
type(self),
|
|
|
|
end - start,
|
|
|
|
)
|
2016-11-04 04:58:25 +00:00
|
|
|
|
2016-03-07 22:39:52 +00:00
|
|
|
# Overwrite properties that have been set in the config file.
|
2017-02-16 03:47:30 +00:00
|
|
|
if DATA_CUSTOMIZE in self.hass.data:
|
|
|
|
attr.update(self.hass.data[DATA_CUSTOMIZE].get(self.entity_id))
|
2015-04-15 02:57:32 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
# Convert temperature if we detect one
|
2016-07-31 20:24:49 +00:00
|
|
|
try:
|
|
|
|
unit_of_measure = attr.get(ATTR_UNIT_OF_MEASUREMENT)
|
2016-11-23 01:38:04 +00:00
|
|
|
units = self.hass.config.units
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
unit_of_measure in (TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
|
|
|
and unit_of_measure != units.temperature_unit
|
|
|
|
):
|
|
|
|
prec = len(state) - state.index(".") - 1 if "." in state else 0
|
2016-11-23 01:38:04 +00:00
|
|
|
temp = units.temperature(float(state), unit_of_measure)
|
|
|
|
state = str(round(temp) if prec == 0 else round(temp, prec))
|
2016-08-05 05:37:30 +00:00
|
|
|
attr[ATTR_UNIT_OF_MEASUREMENT] = units.temperature_unit
|
2016-07-31 20:24:49 +00:00
|
|
|
except ValueError:
|
|
|
|
# Could not convert state to float
|
|
|
|
pass
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
self._context is not None
|
|
|
|
and dt_util.utcnow() - self._context_set > self.context_recent_time
|
|
|
|
):
|
2018-08-20 15:39:53 +00:00
|
|
|
self._context = None
|
|
|
|
self._context_set = None
|
|
|
|
|
2016-09-30 19:57:24 +00:00
|
|
|
self.hass.states.async_set(
|
2019-07-31 19:25:30 +00:00
|
|
|
self.entity_id, state, attr, self.force_update, self._context
|
|
|
|
)
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2016-11-16 16:26:29 +00:00
|
|
|
def schedule_update_ha_state(self, force_refresh=False):
|
2018-01-27 19:58:27 +00:00
|
|
|
"""Schedule an update ha state change task.
|
2016-11-16 05:06:50 +00:00
|
|
|
|
2019-03-09 17:52:22 +00:00
|
|
|
Scheduling the update avoids executor deadlocks.
|
|
|
|
|
|
|
|
Entity state and attributes are read when the update ha state change
|
|
|
|
task is executed.
|
|
|
|
If state is changed more than once before the ha state change task has
|
|
|
|
been executed, the intermediate state transitions will be missed.
|
2016-11-16 05:06:50 +00:00
|
|
|
"""
|
|
|
|
self.hass.add_job(self.async_update_ha_state(force_refresh))
|
|
|
|
|
2017-11-17 05:03:05 +00:00
|
|
|
@callback
|
2017-09-12 08:01:03 +00:00
|
|
|
def async_schedule_update_ha_state(self, force_refresh=False):
|
2019-03-09 17:52:22 +00:00
|
|
|
"""Schedule an update ha state change task.
|
|
|
|
|
|
|
|
This method must be run in the event loop.
|
|
|
|
Scheduling the update avoids executor deadlocks.
|
|
|
|
|
|
|
|
Entity state and attributes are read when the update ha state change
|
|
|
|
task is executed.
|
|
|
|
If state is changed more than once before the ha state change task has
|
|
|
|
been executed, the intermediate state transitions will be missed.
|
|
|
|
"""
|
2019-02-09 18:41:40 +00:00
|
|
|
self.hass.async_create_task(self.async_update_ha_state(force_refresh))
|
2017-09-12 08:01:03 +00:00
|
|
|
|
2018-10-01 06:56:50 +00:00
|
|
|
async def async_device_update(self, warning=True):
|
2017-10-22 15:40:00 +00:00
|
|
|
"""Process 'update' or 'async_update' from entity.
|
|
|
|
|
|
|
|
This method is a coroutine.
|
|
|
|
"""
|
|
|
|
if self._update_staged:
|
|
|
|
return
|
|
|
|
self._update_staged = True
|
|
|
|
|
|
|
|
# Process update sequential
|
|
|
|
if self.parallel_updates:
|
2018-10-01 06:56:50 +00:00
|
|
|
await self.parallel_updates.acquire()
|
2017-10-22 15:40:00 +00:00
|
|
|
|
|
|
|
if warning:
|
|
|
|
update_warn = self.hass.loop.call_later(
|
2019-07-31 19:25:30 +00:00
|
|
|
SLOW_UPDATE_WARNING,
|
|
|
|
_LOGGER.warning,
|
|
|
|
"Update of %s is taking over %s seconds",
|
|
|
|
self.entity_id,
|
|
|
|
SLOW_UPDATE_WARNING,
|
2017-10-22 15:40:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
2018-06-07 19:31:21 +00:00
|
|
|
# pylint: disable=no-member
|
2019-07-31 19:25:30 +00:00
|
|
|
if hasattr(self, "async_update"):
|
2018-10-01 06:56:50 +00:00
|
|
|
await self.async_update()
|
2019-07-31 19:25:30 +00:00
|
|
|
elif hasattr(self, "update"):
|
2018-10-09 14:54:38 +00:00
|
|
|
await self.hass.async_add_executor_job(self.update)
|
2017-10-22 15:40:00 +00:00
|
|
|
finally:
|
|
|
|
self._update_staged = False
|
|
|
|
if warning:
|
|
|
|
update_warn.cancel()
|
|
|
|
if self.parallel_updates:
|
|
|
|
self.parallel_updates.release()
|
|
|
|
|
2018-07-24 12:12:53 +00:00
|
|
|
@callback
|
2019-07-21 16:59:02 +00:00
|
|
|
def async_on_remove(self, func: CALLBACK_TYPE) -> None:
|
2018-07-24 12:12:53 +00:00
|
|
|
"""Add a function to call when entity removed."""
|
|
|
|
if self._on_remove is None:
|
|
|
|
self._on_remove = []
|
|
|
|
self._on_remove.append(func)
|
|
|
|
|
2018-02-25 11:38:46 +00:00
|
|
|
async def async_remove(self):
|
2018-01-23 06:54:41 +00:00
|
|
|
"""Remove entity from Home Assistant."""
|
2019-06-26 16:22:51 +00:00
|
|
|
await self.async_internal_will_remove_from_hass()
|
2018-11-28 12:16:43 +00:00
|
|
|
await self.async_will_remove_from_hass()
|
2018-10-25 17:57:36 +00:00
|
|
|
|
2018-07-24 12:12:53 +00:00
|
|
|
if self._on_remove is not None:
|
|
|
|
while self._on_remove:
|
|
|
|
self._on_remove.pop()()
|
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
self.hass.states.async_remove(self.entity_id)
|
2016-09-04 15:15:52 +00:00
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
async def async_added_to_hass(self) -> None:
|
|
|
|
"""Run when entity about to be added to hass.
|
|
|
|
|
|
|
|
To be extended by integrations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
async def async_will_remove_from_hass(self) -> None:
|
|
|
|
"""Run when entity will be removed from hass.
|
|
|
|
|
|
|
|
To be extended by integrations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
async def async_internal_added_to_hass(self) -> None:
|
|
|
|
"""Run when entity about to be added to hass.
|
2018-07-24 12:12:53 +00:00
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
Not to be extended by integrations.
|
|
|
|
"""
|
|
|
|
if self.registry_entry is not None:
|
2019-07-21 16:59:02 +00:00
|
|
|
assert self.hass is not None
|
2019-07-31 19:25:30 +00:00
|
|
|
self.async_on_remove(
|
|
|
|
self.hass.bus.async_listen(
|
|
|
|
EVENT_ENTITY_REGISTRY_UPDATED, self._async_registry_updated
|
|
|
|
)
|
|
|
|
)
|
2019-06-26 16:22:51 +00:00
|
|
|
|
|
|
|
async def async_internal_will_remove_from_hass(self) -> None:
|
|
|
|
"""Run when entity will be removed from hass.
|
|
|
|
|
|
|
|
Not to be extended by integrations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
async def _async_registry_updated(self, event):
|
|
|
|
"""Handle entity registry update."""
|
|
|
|
data = event.data
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
data["action"] != "update"
|
|
|
|
or data.get("old_entity_id", data["entity_id"]) != self.entity_id
|
|
|
|
):
|
2018-07-24 12:12:53 +00:00
|
|
|
return
|
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
ent_reg = await self.hass.helpers.entity_registry.async_get_registry()
|
|
|
|
old = self.registry_entry
|
2019-07-31 19:25:30 +00:00
|
|
|
self.registry_entry = ent_reg.async_get(data["entity_id"])
|
2018-07-24 12:12:53 +00:00
|
|
|
|
2019-08-23 00:32:43 +00:00
|
|
|
if self.registry_entry.disabled_by is not None:
|
|
|
|
await self.async_remove()
|
|
|
|
return
|
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
if self.registry_entry.entity_id == old.entity_id:
|
|
|
|
self.async_write_ha_state()
|
|
|
|
return
|
2018-02-24 18:53:59 +00:00
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
await self.async_remove()
|
2018-11-28 12:16:43 +00:00
|
|
|
|
2019-06-26 16:22:51 +00:00
|
|
|
self.entity_id = self.registry_entry.entity_id
|
|
|
|
await self.platform.async_add_entities([self])
|
2018-11-28 12:16:43 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
def __eq__(self, other):
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the comparison."""
|
2018-01-30 09:39:39 +00:00
|
|
|
if not isinstance(other, self.__class__):
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Can only decide equality if both have a unique id
|
|
|
|
if self.unique_id is None or other.unique_id is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Ensure they belong to the same platform
|
|
|
|
if self.platform is not None or other.platform is not None:
|
|
|
|
if self.platform is None or other.platform is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self.platform.platform != other.platform.platform:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return self.unique_id == other.unique_id
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-09-24 21:20:04 +00:00
|
|
|
def __repr__(self) -> str:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return the representation."""
|
2020-01-03 13:47:06 +00:00
|
|
|
return f"<Entity {self.name}: {self.state}>"
|
2015-03-22 01:49:30 +00:00
|
|
|
|
Switch on/off all lights, and wait for the result (#27078)
* Switch on/off all lights, and wait for the result
Reuses the parallel_updates semaphore.
This is a small crutch which serializes platforms which already do tis
for updates. Platforms which can parallelize everything, this makes it
go faster
* Fix broken unittest
With manual validation, with help from @frenck, we found out that the
assertions are wrong and the test should be failing.
The sequence requested is
OFF
ON
without cancelation, this code should result in:
off,off,off,on,on,on
testable, by adding a `await hass.async_block_till_done()` between the
off and on call.
with cancelation. there should be less off call's so
off,on,on,on
* Adding tests for async_request_call
* Process review feedback
* Switch gather with wait
* :shirt: running black
2019-10-06 15:23:12 +00:00
|
|
|
# call an requests
|
|
|
|
async def async_request_call(self, coro):
|
|
|
|
"""Process request batched."""
|
|
|
|
|
|
|
|
if self.parallel_updates:
|
|
|
|
await self.parallel_updates.acquire()
|
|
|
|
|
|
|
|
try:
|
|
|
|
await coro
|
|
|
|
finally:
|
|
|
|
if self.parallel_updates:
|
|
|
|
self.parallel_updates.release()
|
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
|
|
|
|
class ToggleEntity(Entity):
|
2016-03-25 19:35:38 +00:00
|
|
|
"""An abstract class for entities that can be turned on and off."""
|
2016-01-31 02:03:51 +00:00
|
|
|
|
2015-03-22 01:49:30 +00:00
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def state(self) -> str:
|
2016-01-31 02:03:51 +00:00
|
|
|
"""Return the state."""
|
2015-03-22 01:49:30 +00:00
|
|
|
return STATE_ON if self.is_on else STATE_OFF
|
|
|
|
|
|
|
|
@property
|
2016-07-28 03:33:49 +00:00
|
|
|
def is_on(self) -> bool:
|
2016-03-07 22:39:52 +00:00
|
|
|
"""Return True if entity is on."""
|
2016-06-25 04:27:40 +00:00
|
|
|
raise NotImplementedError()
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-08-15 15:53:25 +00:00
|
|
|
def turn_on(self, **kwargs: Any) -> None:
|
2016-11-04 01:32:14 +00:00
|
|
|
"""Turn the entity on."""
|
2016-11-16 05:06:50 +00:00
|
|
|
raise NotImplementedError()
|
2016-11-04 01:32:14 +00:00
|
|
|
|
|
|
|
def async_turn_on(self, **kwargs):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Turn the entity on.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.hass.async_add_job(ft.partial(self.turn_on, **kwargs))
|
2015-03-22 01:49:30 +00:00
|
|
|
|
2019-08-15 15:53:25 +00:00
|
|
|
def turn_off(self, **kwargs: Any) -> None:
|
2016-11-04 01:32:14 +00:00
|
|
|
"""Turn the entity off."""
|
2016-11-16 05:06:50 +00:00
|
|
|
raise NotImplementedError()
|
2016-11-04 01:32:14 +00:00
|
|
|
|
|
|
|
def async_turn_off(self, **kwargs):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Turn the entity off.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.hass.async_add_job(ft.partial(self.turn_off, **kwargs))
|
2016-01-16 15:45:05 +00:00
|
|
|
|
2019-08-15 15:53:25 +00:00
|
|
|
def toggle(self, **kwargs: Any) -> None:
|
2016-11-04 01:32:14 +00:00
|
|
|
"""Toggle the entity."""
|
|
|
|
if self.is_on:
|
2017-07-14 03:04:23 +00:00
|
|
|
self.turn_off(**kwargs)
|
2016-11-04 01:32:14 +00:00
|
|
|
else:
|
2017-07-14 03:04:23 +00:00
|
|
|
self.turn_on(**kwargs)
|
2016-11-04 01:32:14 +00:00
|
|
|
|
2017-07-14 03:04:23 +00:00
|
|
|
def async_toggle(self, **kwargs):
|
2016-12-26 13:10:23 +00:00
|
|
|
"""Toggle the entity.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
2016-01-17 21:59:22 +00:00
|
|
|
if self.is_on:
|
2017-07-14 03:04:23 +00:00
|
|
|
return self.async_turn_off(**kwargs)
|
|
|
|
return self.async_turn_on(**kwargs)
|