""" IMAP sensor support. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.imap/ """ import asyncio import logging import async_timeout import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['aioimaplib==0.7.13'] CONF_SERVER = 'server' CONF_FOLDER = 'folder' DEFAULT_PORT = 993 ICON = 'mdi:email-outline' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_SERVER): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_FOLDER, default='INBOX'): cv.string, }) async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the IMAP platform.""" sensor = ImapSensor(config.get(CONF_NAME), config.get(CONF_USERNAME), config.get(CONF_PASSWORD), config.get(CONF_SERVER), config.get(CONF_PORT), config.get(CONF_FOLDER)) if not await sensor.connection(): raise PlatformNotReady hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.shutdown()) async_add_devices([sensor], True) class ImapSensor(Entity): """Representation of an IMAP sensor.""" def __init__(self, name, user, password, server, port, folder): """Initialize the sensor.""" self._name = name or user self._user = user self._password = password self._server = server self._port = port self._folder = folder self._unread_count = 0 self._connection = None self._does_push = None self._idle_loop_task = None async def async_added_to_hass(self): """Handle when an entity is about to be added to Home Assistant.""" if not self.should_poll: self._idle_loop_task = self.hass.loop.create_task(self.idle_loop()) @property def name(self): """Return the name of the sensor.""" return self._name @property def icon(self): """Return the icon to use in the frontend.""" return ICON @property def state(self): """Return the number of unread emails.""" return self._unread_count @property def available(self): """Return the availability of the device.""" return self._connection is not None @property def should_poll(self): """Return if polling is needed.""" return not self._does_push async def connection(self): """Return a connection to the server, establishing it if necessary.""" import aioimaplib if self._connection is None: try: self._connection = aioimaplib.IMAP4_SSL( self._server, self._port) await self._connection.wait_hello_from_server() await self._connection.login(self._user, self._password) await self._connection.select(self._folder) self._does_push = self._connection.has_capability('IDLE') except (aioimaplib.AioImapException, asyncio.TimeoutError): self._connection = None return self._connection async def idle_loop(self): """Wait for data pushed from server.""" import aioimaplib while True: try: if await self.connection(): await self.refresh_unread_count() await self.async_update_ha_state() idle = await self._connection.idle_start() await self._connection.wait_server_push() self._connection.idle_done() with async_timeout.timeout(10): await idle else: await self.async_update_ha_state() except (aioimaplib.AioImapException, asyncio.TimeoutError): self.disconnected() async def async_update(self): """Periodic polling of state.""" import aioimaplib try: if await self.connection(): await self.refresh_unread_count() except (aioimaplib.AioImapException, asyncio.TimeoutError): self.disconnected() async def refresh_unread_count(self): """Check the number of unread emails.""" if self._connection: await self._connection.noop() _, lines = await self._connection.search('UnSeen UnDeleted') self._unread_count = len(lines[0].split()) def disconnected(self): """Forget the connection after it was lost.""" _LOGGER.warning("Lost %s (will attempt to reconnect)", self._server) self._connection = None async def shutdown(self): """Close resources.""" if self._connection: if self._connection.has_pending_idle(): self._connection.idle_done() await self._connection.logout() if self._idle_loop_task: self._idle_loop_task.cancel()