text: """
  homeassistant.components.scene
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  Allows users to set and activate scenes within Home Assistant.
  
  A scene is a set of states that describe how you want certain entities to be.
  For example, light A should be red with 100 brightness. Light B should be on.
  
  A scene is active if all states of the scene match the real states.
  
  If a scene is manually activated it will store the previous state of the
  entities. These will be restored when the state is deactivated manually.
  
  If one of the enties that are being tracked change state on its own, the
  old state will not be restored when it is being deactivated.
  """
  import logging
  from collections import namedtuple
  
  from homeassistant.core import State
  from homeassistant.helpers.event import track_state_change
  from homeassistant.helpers.entity import ToggleEntity
  from homeassistant.helpers.entity_component import EntityComponent
  from homeassistant.helpers.state import reproduce_state
  from homeassistant.const import (
      ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF)
  
  DOMAIN = 'scene'
  DEPENDENCIES = ['group']
  
  ATTR_ACTIVE_REQUESTED = "active_requested"
  
  CONF_ENTITIES = "entities"
  
  SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
  
  
  def setup(hass, config):
      """ Sets up scenes. """
  
      logger = logging.getLogger(__name__)
  
      scene_configs = config.get(DOMAIN)
  
      if not isinstance(scene_configs, list):
          logger.error('Scene config should be a list of scenes')
          return False
  
      component = EntityComponent(logger, DOMAIN, hass)
  
      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. """
          target_scenes = component.extract_from_service(service)
  
          for scene in target_scenes:
              if service.service == SERVICE_TURN_ON:
                  scene.turn_on()
              else:
                  scene.turn_off()
  
      hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_scene_service)
      hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service)
  
      return True
  
  
  def _process_config(scene_config):
      """ Process passed in config into a format to work with. """
      name = scene_config.get('name')
      states = {}
      c_entities = dict(scene_config.get(CONF_ENTITIES, {}))
  
      for entity_id in c_entities:
          if isinstance(c_entities[entity_id], dict):
              state = c_entities[entity_id].pop('state', None)
              attributes = c_entities[entity_id]
          else:
              state = c_entities[entity_id]
              attributes = {}
  
          # YAML translates 'on' to a boolean
          # http://yaml.org/type/bool.html
          if isinstance(state, bool):
              state = STATE_ON if state else STATE_OFF
          else:
              state = str(state)
  
          states[entity_id.lower()] = State(entity_id, state, attributes)
  
      return SceneConfig(name, states)
  
  
  class Scene(ToggleEntity):
      """ A scene is a group of entities and the states we want them to be. """
  
      def __init__(self, hass, scene_config):
          self.hass = hass
          self.scene_config = scene_config
  
          self.is_active = False
          self.prev_states = None
          self.ignore_updates = False
  
          track_state_change(
              self.hass, self.entity_ids, self.entity_state_changed)
  
          self.update()
  
      @property
      def should_poll(self):
          return False
  
      @property
      def name(self):
          return self.scene_config.name
  
      @property
      def is_on(self):
          return self.is_active
  
      @property
      def entity_ids(self):
          """ Entity IDs part of this scene. """
          return self.scene_config.states.keys()
  
      @property
      def state_attributes(self):
          """ Scene state attributes. """
          return {
              ATTR_ENTITY_ID: list(self.entity_ids),
              ATTR_ACTIVE_REQUESTED: self.prev_states is not None,
          }
  
      def turn_on(self):
          """ Activates scene. Tries to get entities into requested state. """
          self.prev_states = tuple(self.hass.states.get(entity_id)
                                   for entity_id in self.entity_ids)
  
          self._reproduce_state(self.scene_config.states.values())
  
      def turn_off(self):
          """ Deactivates scene and restores old states. """
          if self.prev_states:
              self._reproduce_state(self.prev_states)
              self.prev_states = None
  
      def entity_state_changed(self, entity_id, old_state, new_state):
          """ Called when an entity part of this scene changes state. """
          if self.ignore_updates:
              return
  
          # If new state is not what we expect, it can never be active
          if self._state_as_requested(new_state):
              self.update()
          else:
              self.is_active = False
              self.prev_states = None
  
          self.update_ha_state()
  
      def update(self):
          """
          Update if the scene is active.
  
          Will look at each requested state and see if the current entity
          has the same state and has at least the same attributes with the
          same values. The real state can have more attributes.
          """
          self.is_active = all(
              self._state_as_requested(self.hass.states.get(entity_id))
              for entity_id in self.entity_ids)
  
      def _state_as_requested(self, cur_state):
          """ Returns if given state is as requested. """
          state = self.scene_config.states.get(cur_state and cur_state.entity_id)
  
          return (cur_state is not None and state.state == cur_state.state and
                  all(value == cur_state.attributes.get(key)
                      for key, value in state.attributes.items()))
  
      def _reproduce_state(self, states):
          """ Wraps reproduce state with Scence specific logic. """
          self.ignore_updates = True
          reproduce_state(self.hass, states, True)
          self.ignore_updates = False
  
          self.update_ha_state(True)
  