diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py index 889d7271ef1..28296859f80 100644 --- a/homeassistant/components/scene.py +++ b/homeassistant/components/scene.py @@ -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. """ diff --git a/homeassistant/helpers/__init__.py b/homeassistant/helpers/__init__.py index a677ef8d0c7..4d92df43282 100644 --- a/homeassistant/helpers/__init__.py +++ b/homeassistant/helpers/__init__.py @@ -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): diff --git a/homeassistant/helpers/device.py b/homeassistant/helpers/device.py index 5d400124089..4c713693c43 100644 --- a/homeassistant/helpers/device.py +++ b/homeassistant/helpers/device.py @@ -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 "".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') diff --git a/homeassistant/helpers/device_component.py b/homeassistant/helpers/device_component.py index a0b61924080..248297a9694 100644 --- a/homeassistant/helpers/device_component.py +++ b/homeassistant/helpers/device_component.py @@ -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') diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py new file mode 100644 index 00000000000..a8ee712b0f7 --- /dev/null +++ b/homeassistant/helpers/entity.py @@ -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 "".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 diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py new file mode 100644 index 00000000000..21093d89804 --- /dev/null +++ b/homeassistant/helpers/entity_component.py @@ -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)