text: """Support for Minut Point."""
  import asyncio
  import logging
  
  import voluptuous as vol
  
  from homeassistant import config_entries
  from homeassistant.config_entries import ConfigEntry
  from homeassistant.const import CONF_TOKEN, CONF_WEBHOOK_ID
  from homeassistant.helpers import config_validation as cv
  from homeassistant.helpers.dispatcher import (
      async_dispatcher_connect, async_dispatcher_send)
  from homeassistant.helpers.entity import Entity
  from homeassistant.helpers.event import async_track_time_interval
  from homeassistant.helpers.typing import HomeAssistantType
  from homeassistant.util.dt import as_local, parse_datetime, utc_from_timestamp
  
  from . import config_flow  # noqa  pylint_disable=unused-import
  from .const import (
      CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, POINT_DISCOVERY_NEW,
      SCAN_INTERVAL, SIGNAL_UPDATE_ENTITY, SIGNAL_WEBHOOK)
  
  _LOGGER = logging.getLogger(__name__)
  
  CONF_CLIENT_ID = 'client_id'
  CONF_CLIENT_SECRET = 'client_secret'
  
  DATA_CONFIG_ENTRY_LOCK = 'point_config_entry_lock'
  CONFIG_ENTRY_IS_SETUP = 'point_config_entry_is_setup'
  
  CONFIG_SCHEMA = vol.Schema(
      {
          DOMAIN:
          vol.Schema({
              vol.Required(CONF_CLIENT_ID): cv.string,
              vol.Required(CONF_CLIENT_SECRET): cv.string,
          })
      },
      extra=vol.ALLOW_EXTRA,
  )
  
  
  async def async_setup(hass, config):
      """Set up the Minut Point component."""
      if DOMAIN not in config:
          return True
  
      conf = config[DOMAIN]
  
      config_flow.register_flow_implementation(
          hass, DOMAIN, conf[CONF_CLIENT_ID],
          conf[CONF_CLIENT_SECRET])
  
      hass.async_create_task(
          hass.config_entries.flow.async_init(
              DOMAIN,
              context={'source': config_entries.SOURCE_IMPORT},
          ))
  
      return True
  
  
  async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
      """Set up Point from a config entry."""
      from pypoint import PointSession
  
      def token_saver(token):
          _LOGGER.debug('Saving updated token')
          entry.data[CONF_TOKEN] = token
          hass.config_entries.async_update_entry(entry, data={**entry.data})
  
      # Force token update.
      entry.data[CONF_TOKEN]['expires_in'] = -1
      session = PointSession(
          entry.data['refresh_args']['client_id'],
          token=entry.data[CONF_TOKEN],
          auto_refresh_kwargs=entry.data['refresh_args'],
          token_saver=token_saver,
      )
  
      if not session.is_authorized:
          _LOGGER.error('Authentication Error')
          return False
  
      hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
      hass.data[CONFIG_ENTRY_IS_SETUP] = set()
  
      await async_setup_webhook(hass, entry, session)
      client = MinutPointClient(hass, entry, session)
      hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: client})
      hass.async_create_task(client.update())
  
      return True
  
  
  async def async_setup_webhook(hass: HomeAssistantType, entry: ConfigEntry,
                                session):
      """Set up a webhook to handle binary sensor events."""
      if CONF_WEBHOOK_ID not in entry.data:
          entry.data[CONF_WEBHOOK_ID] = \
              hass.components.webhook.async_generate_id()
          entry.data[CONF_WEBHOOK_URL] = \
              hass.components.webhook.async_generate_url(
                  entry.data[CONF_WEBHOOK_ID])
          _LOGGER.info('Registering new webhook at: %s',
                       entry.data[CONF_WEBHOOK_URL])
          hass.config_entries.async_update_entry(
              entry, data={
                  **entry.data,
              })
      await hass.async_add_executor_job(
          session.update_webhook,
          entry.data[CONF_WEBHOOK_URL],
          entry.data[CONF_WEBHOOK_ID],
          ['*'])
  
      hass.components.webhook.async_register(
          DOMAIN, 'Point', entry.data[CONF_WEBHOOK_ID], handle_webhook)
  
  
  async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
      """Unload a config entry."""
      hass.components.webhook.async_unregister(entry.data[CONF_WEBHOOK_ID])
      session = hass.data[DOMAIN].pop(entry.entry_id)
      await hass.async_add_executor_job(session.remove_webhook)
  
      if not hass.data[DOMAIN]:
          hass.data.pop(DOMAIN)
  
      for component in ('binary_sensor', 'sensor'):
          await hass.config_entries.async_forward_entry_unload(
              entry, component)
  
      return True
  
  
  async def handle_webhook(hass, webhook_id, request):
      """Handle webhook callback."""
      try:
          data = await request.json()
          _LOGGER.debug("Webhook %s: %s", webhook_id, data)
      except ValueError:
          return None
  
      if isinstance(data, dict):
          data['webhook_id'] = webhook_id
          async_dispatcher_send(hass, SIGNAL_WEBHOOK, data, data.get('hook_id'))
      hass.bus.async_fire(EVENT_RECEIVED, data)
  
  
  class MinutPointClient():
      """Get the latest data and update the states."""
  
      def __init__(self, hass: HomeAssistantType, config_entry: ConfigEntry,
                   session):
          """Initialize the Minut data object."""
          self._known_devices = set()
          self._known_homes = set()
          self._hass = hass
          self._config_entry = config_entry
          self._is_available = True
          self._client = session
  
          async_track_time_interval(self._hass, self.update, SCAN_INTERVAL)
  
      async def update(self, *args):
          """Periodically poll the cloud for current state."""
          await self._sync()
  
      async def _sync(self):
          """Update local list of devices."""
          if not await self._hass.async_add_executor_job(
                  self._client.update) and self._is_available:
              self._is_available = False
              _LOGGER.warning("Device is unavailable")
              return
  
          async def new_device(device_id, component):
              """Load new device."""
              config_entries_key = '{}.{}'.format(component, DOMAIN)
              async with self._hass.data[DATA_CONFIG_ENTRY_LOCK]:
                  if config_entries_key not in self._hass.data[
                          CONFIG_ENTRY_IS_SETUP]:
                      await self._hass.config_entries.async_forward_entry_setup(
                          self._config_entry, component)
                      self._hass.data[CONFIG_ENTRY_IS_SETUP].add(
                          config_entries_key)
  
              async_dispatcher_send(
                  self._hass, POINT_DISCOVERY_NEW.format(component, DOMAIN),
                  device_id)
  
          self._is_available = True
          for home_id in self._client.homes:
              if home_id not in self._known_homes:
                  await new_device(home_id, 'alarm_control_panel')
                  self._known_homes.add(home_id)
          for device in self._client.devices:
              if device.device_id not in self._known_devices:
                  for component in ('sensor', 'binary_sensor'):
                      await new_device(device.device_id, component)
                  self._known_devices.add(device.device_id)
          async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
  
      def device(self, device_id):
          """Return device representation."""
          return self._client.device(device_id)
  
      def is_available(self, device_id):
          """Return device availability."""
          return device_id in self._client.device_ids
  
      def remove_webhook(self):
          """Remove the session webhook."""
          return self._client.remove_webhook()
  
      @property
      def homes(self):
          """Return known homes."""
          return self._client.homes
  
      def alarm_disarm(self, home_id):
          """Send alarm disarm command."""
          return self._client.alarm_disarm(home_id)
  
      def alarm_arm(self, home_id):
          """Send alarm arm command."""
          return self._client.alarm_arm(home_id)
  
  
  class MinutPointEntity(Entity):
      """Base Entity used by the sensors."""
  
      def __init__(self, point_client, device_id, device_class):
          """Initialize the entity."""
          self._async_unsub_dispatcher_connect = None
          self._client = point_client
          self._id = device_id
          self._name = self.device.name
          self._device_class = device_class
          self._updated = utc_from_timestamp(0)
          self._value = None
  
      def __str__(self):
          """Return string representation of device."""
          return "MinutPoint {}".format(self.name)
  
      async def async_added_to_hass(self):
          """Call when entity is added to hass."""
          _LOGGER.debug('Created device %s', self)
          self._async_unsub_dispatcher_connect = async_dispatcher_connect(
              self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback)
          await self._update_callback()
  
      async def async_will_remove_from_hass(self):
          """Disconnect dispatcher listener when removed."""
          if self._async_unsub_dispatcher_connect:
              self._async_unsub_dispatcher_connect()
  
      async def _update_callback(self):
          """Update the value of the sensor."""
          pass
  
      @property
      def available(self):
          """Return true if device is not offline."""
          return self._client.is_available(self.device_id)
  
      @property
      def device(self):
          """Return the representation of the device."""
          return self._client.device(self.device_id)
  
      @property
      def device_class(self):
          """Return the device class."""
          return self._device_class
  
      @property
      def device_id(self):
          """Return the id of the device."""
          return self._id
  
      @property
      def device_state_attributes(self):
          """Return status of device."""
          attrs = self.device.device_status
          attrs['last_heard_from'] = \
              as_local(self.last_update).strftime("%Y-%m-%d %H:%M:%S")
          return attrs
  
      @property
      def device_info(self):
          """Return a device description for device registry."""
          device = self.device.device
          return {
              'connections': {('mac', device['device_mac'])},
              'identifieres': device['device_id'],
              'manufacturer': 'Minut',
              'model': 'Point v{}'.format(device['hardware_version']),
              'name': device['description'],
              'sw_version': device['firmware']['installed'],
              'via_hub': (DOMAIN, device['home']),
          }
  
      @property
      def name(self):
          """Return the display name of this device."""
          return "{} {}".format(self._name, self.device_class.capitalize())
  
      @property
      def is_updated(self):
          """Return true if sensor have been updated."""
          return self.last_update > self._updated
  
      @property
      def last_update(self):
          """Return the last_update time for the device."""
          last_update = parse_datetime(self.device.last_update)
          return last_update
  
      @property
      def should_poll(self):
          """No polling needed for point."""
          return False
  
      @property
      def unique_id(self):
          """Return the unique id of the sensor."""
          return 'point.{}-{}'.format(self._id, self.device_class)
  
      @property
      def value(self):
          """Return the sensor value."""
          return self._value
  