Refactor helper.device to helper.entity
Introduces a minor backwards compatible change: device_component function add_devices is renamed to add_entities.pull/68/head
parent
c8401a3c4d
commit
d3f0210b1a
|
@ -48,8 +48,8 @@ def setup(hass, config):
|
|||
|
||||
component = DeviceComponent(logger, DOMAIN, hass)
|
||||
|
||||
component.add_devices(Scene(hass, _process_config(scene_config))
|
||||
for scene_config in scene_configs)
|
||||
component.add_entities(Scene(hass, _process_config(scene_config))
|
||||
for scene_config in scene_configs)
|
||||
|
||||
def handle_scene_service(service):
|
||||
""" Handles calls to the switch services. """
|
||||
|
|
|
@ -5,9 +5,9 @@ from homeassistant.loader import get_component
|
|||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
|
||||
from homeassistant.util import ensure_unique_string, slugify
|
||||
|
||||
# Deprecated 3/5/2015 - Moved to homeassistant.helpers.device
|
||||
# Deprecated 3/5/2015 - Moved to homeassistant.helpers.entity
|
||||
# pylint: disable=unused-import
|
||||
from .device import Device, ToggleDevice # noqa
|
||||
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
|
||||
|
||||
|
||||
def generate_entity_id(entity_id_format, name, current_ids=None, hass=None):
|
||||
|
|
|
@ -1,139 +1,10 @@
|
|||
"""
|
||||
homeassistant.helpers.device
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides ABC for devices in HA.
|
||||
Deprecated since 3/21/2015 - please use helpers.entity
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant import NoEntitySpecifiedError
|
||||
# pylint: disable=unused-import
|
||||
from .entity import Entity as Device, ToggleEntity as ToggleDevice # noqa
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF,
|
||||
DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
|
||||
class Device(object):
|
||||
""" ABC for Home Assistant devices. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
hass = None
|
||||
entity_id = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""
|
||||
Return True if device has to be polled for state.
|
||||
False if device pushes its state to HA.
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns a unique id. """
|
||||
return "{}.{}".format(self.__class__, id(self))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the device. """
|
||||
return self.get_name()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the device. """
|
||||
return self.get_state()
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
return {}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity if any. """
|
||||
return None
|
||||
|
||||
# DEPRECATION NOTICE:
|
||||
# Device is moving from getters to properties.
|
||||
# For now the new properties will call the old functions
|
||||
# This will be removed in the future.
|
||||
|
||||
def get_name(self):
|
||||
""" Returns the name of the device if any. """
|
||||
return DEVICE_DEFAULT_NAME
|
||||
|
||||
def get_state(self):
|
||||
""" Returns state of the device. """
|
||||
return "Unknown"
|
||||
|
||||
def get_state_attributes(self):
|
||||
""" Returns optional state attributes. """
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
""" Retrieve latest state from the real device. """
|
||||
pass
|
||||
|
||||
def update_ha_state(self, force_refresh=False):
|
||||
"""
|
||||
Updates Home Assistant with current state of device.
|
||||
If force_refresh == True will update device before setting state.
|
||||
"""
|
||||
if self.hass is None:
|
||||
raise RuntimeError("Attribute hass is None for {}".format(self))
|
||||
|
||||
if self.entity_id is None:
|
||||
raise NoEntitySpecifiedError(
|
||||
"No entity specified for device {}".format(self.name))
|
||||
|
||||
if force_refresh:
|
||||
self.update()
|
||||
|
||||
state = str(self.state)
|
||||
attr = self.state_attributes or {}
|
||||
|
||||
if ATTR_FRIENDLY_NAME not in attr and self.name:
|
||||
attr[ATTR_FRIENDLY_NAME] = self.name
|
||||
|
||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
||||
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
||||
|
||||
# Convert temperature if we detect one
|
||||
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
||||
TEMP_FAHRENHEIT):
|
||||
|
||||
state, attr[ATTR_UNIT_OF_MEASUREMENT] = \
|
||||
self.hass.config.temperature(
|
||||
state, attr[ATTR_UNIT_OF_MEASUREMENT])
|
||||
state = str(state)
|
||||
|
||||
return self.hass.states.set(self.entity_id, state, attr)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, Device) and
|
||||
other.unique_id == self.unique_id)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Device {}: {}>".format(self.name, self.state)
|
||||
|
||||
|
||||
class ToggleDevice(Device):
|
||||
""" ABC for devices that can be turned on and off. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state. """
|
||||
return STATE_ON if self.is_on else STATE_OFF
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if device is on. """
|
||||
return False
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turn the device on. """
|
||||
pass
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turn the device off. """
|
||||
pass
|
||||
logging.getLogger(__name__).warning(
|
||||
'This file is deprecated. Please use helpers.entity')
|
||||
|
|
|
@ -1,139 +1,10 @@
|
|||
"""
|
||||
Provides helpers for components that handle devices.
|
||||
Deprecated since 3/21/2015 - please use helpers.entity_component
|
||||
"""
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers import (
|
||||
generate_entity_id, config_per_platform, extract_entity_ids)
|
||||
from homeassistant.components import group, discovery
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
import logging
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = 15
|
||||
# pylint: disable=unused-import
|
||||
from .entity_component import EntityComponent as DeviceComponent # noqa
|
||||
|
||||
|
||||
class DeviceComponent(object):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-arguments
|
||||
"""
|
||||
Helper class that will help a device component manage its devices.
|
||||
"""
|
||||
def __init__(self, logger, domain, hass,
|
||||
scan_interval=DEFAULT_SCAN_INTERVAL,
|
||||
discovery_platforms=None, group_name=None):
|
||||
self.logger = logger
|
||||
self.hass = hass
|
||||
|
||||
self.domain = domain
|
||||
self.entity_id_format = domain + '.{}'
|
||||
self.scan_interval = scan_interval
|
||||
self.discovery_platforms = discovery_platforms
|
||||
self.group_name = group_name
|
||||
|
||||
self.devices = {}
|
||||
self.group = None
|
||||
self.is_polling = False
|
||||
|
||||
def setup(self, config):
|
||||
"""
|
||||
Sets up a full device component:
|
||||
- Loads the platforms from the config
|
||||
- Will listen for supported discovered platforms
|
||||
"""
|
||||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||
for p_type, p_config in \
|
||||
config_per_platform(config, self.domain, self.logger):
|
||||
|
||||
self._setup_platform(p_type, p_config)
|
||||
|
||||
if self.discovery_platforms:
|
||||
discovery.listen(self.hass, self.discovery_platforms.keys(),
|
||||
self._device_discovered)
|
||||
|
||||
def add_devices(self, new_devices):
|
||||
"""
|
||||
Takes in a list of new devices. For each device will see if it already
|
||||
exists. If not, will add it, set it up and push the first state.
|
||||
"""
|
||||
for device in new_devices:
|
||||
if device is not None and device not in self.devices.values():
|
||||
device.hass = self.hass
|
||||
|
||||
device.entity_id = generate_entity_id(
|
||||
self.entity_id_format, device.name, self.devices.keys())
|
||||
|
||||
self.devices[device.entity_id] = device
|
||||
|
||||
device.update_ha_state()
|
||||
|
||||
if self.group is None and self.group_name is not None:
|
||||
self.group = group.Group(self.hass, self.group_name,
|
||||
user_defined=False)
|
||||
|
||||
if self.group is not None:
|
||||
self.group.update_tracked_entity_ids(self.devices.keys())
|
||||
|
||||
self._start_polling()
|
||||
|
||||
def extract_from_service(self, service):
|
||||
"""
|
||||
Takes a service and extracts all known devices.
|
||||
Will return all if no entity IDs given in service.
|
||||
"""
|
||||
if ATTR_ENTITY_ID not in service.data:
|
||||
return self.devices.values()
|
||||
else:
|
||||
return [self.devices[entity_id] for entity_id
|
||||
in extract_entity_ids(self.hass, service)
|
||||
if entity_id in self.devices]
|
||||
|
||||
def _update_device_states(self, now):
|
||||
""" Update the states of all the lights. """
|
||||
self.logger.info("Updating %s states", self.domain)
|
||||
|
||||
for device in self.devices.values():
|
||||
if device.should_poll:
|
||||
device.update_ha_state(True)
|
||||
|
||||
def _device_discovered(self, service, info):
|
||||
""" Called when a device is discovered. """
|
||||
if service not in self.discovery_platforms:
|
||||
return
|
||||
|
||||
self._setup_platform(self.discovery_platforms[service], {}, info)
|
||||
|
||||
def _start_polling(self):
|
||||
""" Start polling device states if necessary. """
|
||||
if self.is_polling or \
|
||||
not any(device.should_poll for device in self.devices.values()):
|
||||
return
|
||||
|
||||
self.is_polling = True
|
||||
|
||||
self.hass.track_time_change(
|
||||
self._update_device_states,
|
||||
second=range(0, 60, self.scan_interval))
|
||||
|
||||
def _setup_platform(self, platform_type, config, discovery_info=None):
|
||||
""" Tries to setup a platform for this component. """
|
||||
platform_name = '{}.{}'.format(self.domain, platform_type)
|
||||
platform = get_component(platform_name)
|
||||
|
||||
if platform is None:
|
||||
self.logger.error('Unable to find platform %s', platform_type)
|
||||
return
|
||||
|
||||
try:
|
||||
platform.setup_platform(
|
||||
self.hass, config, self.add_devices, discovery_info)
|
||||
except AttributeError:
|
||||
# Support old deprecated method for now - 3/1/2015
|
||||
if hasattr(platform, 'get_devices'):
|
||||
self.logger.warning(
|
||||
"Please upgrade %s to return new devices using "
|
||||
"setup_platform. See %s/demo.py for an example.",
|
||||
platform_name, self.domain)
|
||||
self.add_devices(platform.get_devices(self.hass, config))
|
||||
|
||||
else:
|
||||
# AttributeError if setup_platform does not exist
|
||||
self.logger.exception(
|
||||
"Error setting up %s", platform_type)
|
||||
logging.getLogger(__name__).warning(
|
||||
'This file is deprecated. Please use helpers.entity_component')
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
"""
|
||||
homeassistant.helpers.entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides ABC for entities in HA.
|
||||
"""
|
||||
|
||||
from homeassistant import NoEntitySpecifiedError
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF,
|
||||
DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
|
||||
class Entity(object):
|
||||
""" ABC for Home Assistant entities. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
hass = None
|
||||
entity_id = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""
|
||||
Return True if entity has to be polled for state.
|
||||
False if entity pushes its state to HA.
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns a unique id. """
|
||||
return "{}.{}".format(self.__class__, id(self))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the name of the entity. """
|
||||
return self.get_name()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the entity. """
|
||||
return self.get_state()
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes. """
|
||||
return {}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
return None
|
||||
|
||||
# DEPRECATION NOTICE:
|
||||
# Device is moving from getters to properties.
|
||||
# For now the new properties will call the old functions
|
||||
# This will be removed in the future.
|
||||
|
||||
def get_name(self):
|
||||
""" Returns the name of the entity if any. """
|
||||
return DEVICE_DEFAULT_NAME
|
||||
|
||||
def get_state(self):
|
||||
""" Returns state of the entity. """
|
||||
return "Unknown"
|
||||
|
||||
def get_state_attributes(self):
|
||||
""" Returns optional state attributes. """
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
""" Retrieve latest state. """
|
||||
pass
|
||||
|
||||
def update_ha_state(self, force_refresh=False):
|
||||
"""
|
||||
Updates Home Assistant with current state of entity.
|
||||
If force_refresh == True will update entity before setting state.
|
||||
"""
|
||||
if self.hass is None:
|
||||
raise RuntimeError("Attribute hass is None for {}".format(self))
|
||||
|
||||
if self.entity_id is None:
|
||||
raise NoEntitySpecifiedError(
|
||||
"No entity id specified for entity {}".format(self.name))
|
||||
|
||||
if force_refresh:
|
||||
self.update()
|
||||
|
||||
state = str(self.state)
|
||||
attr = self.state_attributes or {}
|
||||
|
||||
if ATTR_FRIENDLY_NAME not in attr and self.name:
|
||||
attr[ATTR_FRIENDLY_NAME] = self.name
|
||||
|
||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
||||
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
||||
|
||||
# Convert temperature if we detect one
|
||||
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
||||
TEMP_FAHRENHEIT):
|
||||
|
||||
state, attr[ATTR_UNIT_OF_MEASUREMENT] = \
|
||||
self.hass.config.temperature(
|
||||
state, attr[ATTR_UNIT_OF_MEASUREMENT])
|
||||
state = str(state)
|
||||
|
||||
return self.hass.states.set(self.entity_id, state, attr)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (isinstance(other, Entity) and
|
||||
other.unique_id == self.unique_id)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Entity {}: {}>".format(self.name, self.state)
|
||||
|
||||
|
||||
class ToggleEntity(Entity):
|
||||
""" ABC for entities that can be turned on and off. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state. """
|
||||
return STATE_ON if self.is_on else STATE_OFF
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" True if entity is on. """
|
||||
return False
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" Turn the entity on. """
|
||||
pass
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" Turn the entity off. """
|
||||
pass
|
|
@ -0,0 +1,142 @@
|
|||
"""
|
||||
homeassistant.helpers.entity_component
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides helpers for components that manage entities.
|
||||
"""
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers import (
|
||||
generate_entity_id, config_per_platform, extract_entity_ids)
|
||||
from homeassistant.components import group, discovery
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = 15
|
||||
|
||||
|
||||
class EntityComponent(object):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-arguments
|
||||
"""
|
||||
Helper class that will help a component manage its entities.
|
||||
"""
|
||||
def __init__(self, logger, domain, hass,
|
||||
scan_interval=DEFAULT_SCAN_INTERVAL,
|
||||
discovery_platforms=None, group_name=None):
|
||||
self.logger = logger
|
||||
self.hass = hass
|
||||
|
||||
self.domain = domain
|
||||
self.entity_id_format = domain + '.{}'
|
||||
self.scan_interval = scan_interval
|
||||
self.discovery_platforms = discovery_platforms
|
||||
self.group_name = group_name
|
||||
|
||||
self.entities = {}
|
||||
self.group = None
|
||||
self.is_polling = False
|
||||
|
||||
def setup(self, config):
|
||||
"""
|
||||
Sets up a full entity component:
|
||||
- Loads the platforms from the config
|
||||
- Will listen for supported discovered platforms
|
||||
"""
|
||||
# Look in config for Domain, Domain 2, Domain 3 etc and load them
|
||||
for p_type, p_config in \
|
||||
config_per_platform(config, self.domain, self.logger):
|
||||
|
||||
self._setup_platform(p_type, p_config)
|
||||
|
||||
if self.discovery_platforms:
|
||||
discovery.listen(self.hass, self.discovery_platforms.keys(),
|
||||
self._entity_discovered)
|
||||
|
||||
def add_entities(self, new_entities):
|
||||
"""
|
||||
Takes in a list of new entities. For each entity will see if it already
|
||||
exists. If not, will add it, set it up and push the first state.
|
||||
"""
|
||||
for entity in new_entities:
|
||||
if entity is not None and entity not in self.entities.values():
|
||||
entity.hass = self.hass
|
||||
|
||||
entity.entity_id = generate_entity_id(
|
||||
self.entity_id_format, entity.name, self.entities.keys())
|
||||
|
||||
self.entities[entity.entity_id] = entity
|
||||
|
||||
entity.update_ha_state()
|
||||
|
||||
if self.group is None and self.group_name is not None:
|
||||
self.group = group.Group(self.hass, self.group_name,
|
||||
user_defined=False)
|
||||
|
||||
if self.group is not None:
|
||||
self.group.update_tracked_entity_ids(self.entities.keys())
|
||||
|
||||
self._start_polling()
|
||||
|
||||
def extract_from_service(self, service):
|
||||
"""
|
||||
Takes a service and extracts all known entities.
|
||||
Will return all if no entity IDs given in service.
|
||||
"""
|
||||
if ATTR_ENTITY_ID not in service.data:
|
||||
return self.entities.values()
|
||||
else:
|
||||
return [self.entities[entity_id] for entity_id
|
||||
in extract_entity_ids(self.hass, service)
|
||||
if entity_id in self.entities]
|
||||
|
||||
def _update_entity_states(self, now):
|
||||
""" Update the states of all the entities. """
|
||||
self.logger.info("Updating %s entities", self.domain)
|
||||
|
||||
for entity in self.entities.values():
|
||||
if entity.should_poll:
|
||||
entity.update_ha_state(True)
|
||||
|
||||
def _entity_discovered(self, service, info):
|
||||
""" Called when a entity is discovered. """
|
||||
if service not in self.discovery_platforms:
|
||||
return
|
||||
|
||||
self._setup_platform(self.discovery_platforms[service], {}, info)
|
||||
|
||||
def _start_polling(self):
|
||||
""" Start polling entities if necessary. """
|
||||
if self.is_polling or \
|
||||
not any(entity.should_poll for entity in self.entities.values()):
|
||||
return
|
||||
|
||||
self.is_polling = True
|
||||
|
||||
self.hass.track_time_change(
|
||||
self._update_entity_states,
|
||||
second=range(0, 60, self.scan_interval))
|
||||
|
||||
def _setup_platform(self, platform_type, config, discovery_info=None):
|
||||
""" Tries to setup a platform for this component. """
|
||||
platform_name = '{}.{}'.format(self.domain, platform_type)
|
||||
platform = get_component(platform_name)
|
||||
|
||||
if platform is None:
|
||||
self.logger.error('Unable to find platform %s', platform_type)
|
||||
return
|
||||
|
||||
try:
|
||||
platform.setup_platform(
|
||||
self.hass, config, self.add_entities, discovery_info)
|
||||
except AttributeError:
|
||||
# Support old deprecated method for now - 3/1/2015
|
||||
if hasattr(platform, 'get_entities'):
|
||||
self.logger.warning(
|
||||
"Please upgrade %s to return new entities using "
|
||||
"setup_platform. See %s/demo.py for an example.",
|
||||
platform_name, self.domain)
|
||||
self.add_devices(platform.get_entities(self.hass, config))
|
||||
|
||||
else:
|
||||
# AttributeError if setup_platform does not exist
|
||||
self.logger.exception(
|
||||
"Error setting up %s", platform_type)
|
Loading…
Reference in New Issue