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
Paulus Schoutsen 2015-03-21 18:49:30 -07:00
parent c8401a3c4d
commit d3f0210b1a
6 changed files with 297 additions and 274 deletions

View File

@ -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. """

View File

@ -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):

View File

@ -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')

View File

@ -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')

View File

@ -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

View File

@ -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)