text: """
  Support for Envisalink devices.
  
  For more details about this component, please refer to the documentation at
  https://home-assistant.io/components/envisalink/
  """
  import logging
  import time
  import voluptuous as vol
  import homeassistant.helpers.config_validation as cv
  from homeassistant.const import EVENT_HOMEASSISTANT_STOP
  from homeassistant.helpers.entity import Entity
  from homeassistant.components.discovery import load_platform
  
  REQUIREMENTS = ['pyenvisalink==2.0', 'pydispatcher==2.0.5']
  
  _LOGGER = logging.getLogger(__name__)
  DOMAIN = 'envisalink'
  
  EVL_CONTROLLER = None
  
  CONF_EVL_HOST = 'host'
  CONF_EVL_PORT = 'port'
  CONF_PANEL_TYPE = 'panel_type'
  CONF_EVL_VERSION = 'evl_version'
  CONF_CODE = 'code'
  CONF_USERNAME = 'user_name'
  CONF_PASS = 'password'
  CONF_EVL_KEEPALIVE = 'keepalive_interval'
  CONF_ZONEDUMP_INTERVAL = 'zonedump_interval'
  CONF_ZONES = 'zones'
  CONF_PARTITIONS = 'partitions'
  
  CONF_ZONENAME = 'name'
  CONF_ZONETYPE = 'type'
  CONF_PARTITIONNAME = 'name'
  CONF_PANIC = 'panic_type'
  
  DEFAULT_PORT = 4025
  DEFAULT_EVL_VERSION = 3
  DEFAULT_KEEPALIVE = 60
  DEFAULT_ZONEDUMP_INTERVAL = 30
  DEFAULT_ZONETYPE = 'opening'
  DEFAULT_PANIC = 'Police'
  
  SIGNAL_ZONE_UPDATE = 'zones_updated'
  SIGNAL_PARTITION_UPDATE = 'partition_updated'
  SIGNAL_KEYPAD_UPDATE = 'keypad_updated'
  
  ZONE_SCHEMA = vol.Schema({
      vol.Required(CONF_ZONENAME): cv.string,
      vol.Optional(CONF_ZONETYPE, default=DEFAULT_ZONETYPE): cv.string})
  
  PARTITION_SCHEMA = vol.Schema({
      vol.Required(CONF_PARTITIONNAME): cv.string})
  
  CONFIG_SCHEMA = vol.Schema({
      DOMAIN: vol.Schema({
          vol.Required(CONF_EVL_HOST): cv.string,
          vol.Required(CONF_PANEL_TYPE):
              vol.All(cv.string, vol.In(['HONEYWELL', 'DSC'])),
          vol.Required(CONF_USERNAME): cv.string,
          vol.Required(CONF_PASS): cv.string,
          vol.Required(CONF_CODE): cv.string,
          vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
          vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
          vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
          vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
          vol.Optional(CONF_EVL_VERSION, default=DEFAULT_EVL_VERSION):
              vol.All(vol.Coerce(int), vol.Range(min=3, max=4)),
          vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE):
              vol.All(vol.Coerce(int), vol.Range(min=15)),
          vol.Optional(CONF_ZONEDUMP_INTERVAL,
                       default=DEFAULT_ZONEDUMP_INTERVAL):
              vol.All(vol.Coerce(int), vol.Range(min=15)),
      }),
  }, extra=vol.ALLOW_EXTRA)
  
  
  # pylint: disable=unused-argument
  def setup(hass, base_config):
      """Common setup for Envisalink devices."""
      from pyenvisalink import EnvisalinkAlarmPanel
      from pydispatch import dispatcher
  
      global EVL_CONTROLLER
  
      config = base_config.get(DOMAIN)
  
      _host = config.get(CONF_EVL_HOST)
      _port = config.get(CONF_EVL_PORT)
      _code = config.get(CONF_CODE)
      _panel_type = config.get(CONF_PANEL_TYPE)
      _panic_type = config.get(CONF_PANIC)
      _version = config.get(CONF_EVL_VERSION)
      _user = config.get(CONF_USERNAME)
      _pass = config.get(CONF_PASS)
      _keep_alive = config.get(CONF_EVL_KEEPALIVE)
      _zone_dump = config.get(CONF_ZONEDUMP_INTERVAL)
      _zones = config.get(CONF_ZONES)
      _partitions = config.get(CONF_PARTITIONS)
      _connect_status = {}
      EVL_CONTROLLER = EnvisalinkAlarmPanel(_host,
                                            _port,
                                            _panel_type,
                                            _version,
                                            _user,
                                            _pass,
                                            _zone_dump,
                                            _keep_alive,
                                            hass.loop)
  
      def login_fail_callback(data):
          """Callback for when the evl rejects our login."""
          _LOGGER.error("The envisalink rejected your credentials.")
          _connect_status['fail'] = 1
  
      def connection_fail_callback(data):
          """Network failure callback."""
          _LOGGER.error("Could not establish a connection with the envisalink.")
          _connect_status['fail'] = 1
  
      def connection_success_callback(data):
          """Callback for a successful connection."""
          _LOGGER.info("Established a connection with the envisalink.")
          _connect_status['success'] = 1
  
      def zones_updated_callback(data):
          """Handle zone timer updates."""
          _LOGGER.info("Envisalink sent a zone update event.  Updating zones...")
          dispatcher.send(signal=SIGNAL_ZONE_UPDATE,
                          sender=None,
                          zone=data)
  
      def alarm_data_updated_callback(data):
          """Handle non-alarm based info updates."""
          _LOGGER.info("Envisalink sent new alarm info. Updating alarms...")
          dispatcher.send(signal=SIGNAL_KEYPAD_UPDATE,
                          sender=None,
                          partition=data)
  
      def partition_updated_callback(data):
          """Handle partition changes thrown by evl (including alarms)."""
          _LOGGER.info("The envisalink sent a partition update event.")
          dispatcher.send(signal=SIGNAL_PARTITION_UPDATE,
                          sender=None,
                          partition=data)
  
      def stop_envisalink(event):
          """Shutdown envisalink connection and thread on exit."""
          _LOGGER.info("Shutting down envisalink.")
          EVL_CONTROLLER.stop()
  
      def start_envisalink(event):
          """Startup process for the Envisalink."""
          hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
          for _ in range(10):
              if 'success' in _connect_status:
                  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
                  return True
              elif 'fail' in _connect_status:
                  return False
              else:
                  time.sleep(1)
  
          _LOGGER.error("Timeout occurred while establishing evl connection.")
          return False
  
      EVL_CONTROLLER.callback_zone_timer_dump = zones_updated_callback
      EVL_CONTROLLER.callback_zone_state_change = zones_updated_callback
      EVL_CONTROLLER.callback_partition_state_change = partition_updated_callback
      EVL_CONTROLLER.callback_keypad_update = alarm_data_updated_callback
      EVL_CONTROLLER.callback_login_failure = login_fail_callback
      EVL_CONTROLLER.callback_login_timeout = connection_fail_callback
      EVL_CONTROLLER.callback_login_success = connection_success_callback
  
      _result = start_envisalink(None)
      if not _result:
          return False
  
      # Load sub-components for Envisalink
      if _partitions:
          load_platform(hass, 'alarm_control_panel', 'envisalink',
                        {CONF_PARTITIONS: _partitions,
                         CONF_CODE: _code,
                         CONF_PANIC: _panic_type}, base_config)
          load_platform(hass, 'sensor', 'envisalink',
                        {CONF_PARTITIONS: _partitions,
                         CONF_CODE: _code}, base_config)
      if _zones:
          load_platform(hass, 'binary_sensor', 'envisalink',
                        {CONF_ZONES: _zones}, base_config)
  
      return True
  
  
  class EnvisalinkDevice(Entity):
      """Representation of an Envisalink device."""
  
      def __init__(self, name, info, controller):
          """Initialize the device."""
          self._controller = controller
          self._info = info
          self._name = name
  
      @property
      def name(self):
          """Return the name of the device."""
          return self._name
  
      @property
      def should_poll(self):
          """No polling needed."""
          return False
  