diff --git a/.coveragerc b/.coveragerc index 8297f7823d5..df4f46b7884 100644 --- a/.coveragerc +++ b/.coveragerc @@ -176,6 +176,9 @@ omit = homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twilio_call.py + homeassistant/components/usps.py + homeassistant/components/*/usps.py + homeassistant/components/velbus.py homeassistant/components/*/velbus.py @@ -519,7 +522,6 @@ omit = homeassistant/components/sensor/uber.py homeassistant/components/sensor/upnp.py homeassistant/components/sensor/ups.py - homeassistant/components/sensor/usps.py homeassistant/components/sensor/vasttrafik.py homeassistant/components/sensor/waqi.py homeassistant/components/sensor/xbox_live.py diff --git a/homeassistant/components/camera/usps.py b/homeassistant/components/camera/usps.py new file mode 100644 index 00000000000..545ea9798de --- /dev/null +++ b/homeassistant/components/camera/usps.py @@ -0,0 +1,94 @@ +""" +Support for a camera made up of usps mail images. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/camera.usps/ +""" +from datetime import timedelta +import logging + +from homeassistant.components.camera import Camera +from homeassistant.components.usps import DATA_USPS + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['usps'] + +SCAN_INTERVAL = timedelta(seconds=10) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up USPS mail camera.""" + if discovery_info is None: + return + + usps = hass.data[DATA_USPS] + add_devices([USPSCamera(usps)]) + + +class USPSCamera(Camera): + """Representation of the images available from USPS.""" + + def __init__(self, usps): + """Initialize the USPS camera images.""" + super().__init__() + + self._usps = usps + self._name = self._usps.name + self._session = self._usps.session + + self._mail_img = [] + self._last_mail = None + self._mail_index = 0 + self._mail_count = 0 + + self._timer = None + + def camera_image(self): + """Update the camera's image if it has changed.""" + self._usps.update() + try: + self._mail_count = len(self._usps.mail) + except TypeError: + # No mail + return None + + if self._usps.mail != self._last_mail: + # Mail items must have changed + self._mail_img = [] + if len(self._usps.mail) >= 1: + self._last_mail = self._usps.mail + for article in self._usps.mail: + _LOGGER.debug("Fetching article image: %s", article) + img = self._session.get(article['image']).content + self._mail_img.append(img) + + try: + return self._mail_img[self._mail_index] + except IndexError: + return None + + @property + def name(self): + """Return the name of this camera.""" + return '{} mail'.format(self._name) + + @property + def model(self): + """Return date of mail as model.""" + try: + return 'Date: {}'.format(self._usps.mail[0]['date']) + except IndexError: + return None + + @property + def should_poll(self): + """Update the mail image index periodically.""" + return True + + def update(self): + """Update mail image index.""" + if self._mail_index < (self._mail_count - 1): + self._mail_index += 1 + else: + self._mail_index = 0 diff --git a/homeassistant/components/sensor/usps.py b/homeassistant/components/sensor/usps.py index 1e818587a72..322c27e2f37 100644 --- a/homeassistant/components/sensor/usps.py +++ b/homeassistant/components/sensor/usps.py @@ -6,65 +6,44 @@ https://home-assistant.io/components/sensor.usps/ """ from collections import defaultdict import logging -from datetime import timedelta -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - ATTR_ATTRIBUTION) +from homeassistant.components.usps import DATA_USPS +from homeassistant.const import ATTR_ATTRIBUTION, ATTR_DATE from homeassistant.helpers.entity import Entity from homeassistant.util import slugify from homeassistant.util.dt import now, parse_datetime -import homeassistant.helpers.config_validation as cv - -REQUIREMENTS = ['myusps==1.1.2'] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'usps' -SCAN_INTERVAL = timedelta(minutes=30) -COOKIE = 'usps_cookies.pickle' +DEPENDENCIES = ['usps'] + STATUS_DELIVERED = 'delivered' -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string -}) - -# pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the USPS platform.""" - import myusps - try: - cookie = hass.config.path(COOKIE) - session = myusps.get_session( - config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - cookie_path=cookie) - except myusps.USPSError: - _LOGGER.exception('Could not connect to My USPS') - return False + if discovery_info is None: + return - add_devices([USPSPackageSensor(session, config.get(CONF_NAME)), - USPSMailSensor(session, config.get(CONF_NAME))], True) + usps = hass.data[DATA_USPS] + add_devices([USPSPackageSensor(usps), + USPSMailSensor(usps)], True) class USPSPackageSensor(Entity): """USPS Package Sensor.""" - def __init__(self, session, name): + def __init__(self, usps): """Initialize the sensor.""" - self._session = session - self._name = name + self._usps = usps + self._name = self._usps.name self._attributes = None self._state = None @property def name(self): """Return the name of the sensor.""" - return '{} packages'.format(self._name or DOMAIN) + return '{} packages'.format(self._name) @property def state(self): @@ -73,16 +52,16 @@ class USPSPackageSensor(Entity): def update(self): """Update device state.""" - import myusps + self._usps.update() status_counts = defaultdict(int) - for package in myusps.get_packages(self._session): + for package in self._usps.packages: status = slugify(package['primary_status']) if status == STATUS_DELIVERED and \ parse_datetime(package['date']).date() < now().date(): continue status_counts[status] += 1 self._attributes = { - ATTR_ATTRIBUTION: myusps.ATTRIBUTION + ATTR_ATTRIBUTION: self._usps.attribution } self._attributes.update(status_counts) self._state = sum(status_counts.values()) @@ -97,21 +76,26 @@ class USPSPackageSensor(Entity): """Icon to use in the frontend.""" return 'mdi:package-variant-closed' + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return 'packages' + class USPSMailSensor(Entity): """USPS Mail Sensor.""" - def __init__(self, session, name): + def __init__(self, usps): """Initialize the sensor.""" - self._session = session - self._name = name + self._usps = usps + self._name = self._usps.name self._attributes = None self._state = None @property def name(self): """Return the name of the sensor.""" - return '{} mail'.format(self._name or DOMAIN) + return '{} mail'.format(self._name) @property def state(self): @@ -120,18 +104,29 @@ class USPSMailSensor(Entity): def update(self): """Update device state.""" - import myusps - self._state = len(myusps.get_mail(self._session)) + self._usps.update() + if self._usps.mail is not None: + self._state = len(self._usps.mail) + else: + self._state = 0 @property def device_state_attributes(self): """Return the state attributes.""" - import myusps - return { - ATTR_ATTRIBUTION: myusps.ATTRIBUTION - } + attr = {} + attr[ATTR_ATTRIBUTION] = self._usps.attribution + try: + attr[ATTR_DATE] = self._usps.mail[0]['date'] + except IndexError: + pass + return attr @property def icon(self): """Icon to use in the frontend.""" return 'mdi:mailbox' + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return 'pieces' diff --git a/homeassistant/components/usps.py b/homeassistant/components/usps.py new file mode 100644 index 00000000000..fdafbbc3587 --- /dev/null +++ b/homeassistant/components/usps.py @@ -0,0 +1,85 @@ +""" +Support for USPS packages and mail. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/usps/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.const import ( + CONF_NAME, CONF_USERNAME, CONF_PASSWORD) +from homeassistant.helpers import (config_validation as cv, discovery) +from homeassistant.util import Throttle +from homeassistant.util.dt import now + +REQUIREMENTS = ['myusps==1.1.3'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'usps' +DATA_USPS = 'data_usps' +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) +COOKIE = 'usps_cookies.pickle' + +USPS_TYPE = ['sensor', 'camera'] + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DOMAIN): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Use config values to set up a function enabling status retrieval.""" + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + name = conf.get(CONF_NAME) + + import myusps + try: + cookie = hass.config.path(COOKIE) + session = myusps.get_session(username, password, cookie_path=cookie) + except myusps.USPSError: + _LOGGER.exception('Could not connect to My USPS') + return False + + hass.data[DATA_USPS] = USPSData(session, name) + + for component in USPS_TYPE: + discovery.load_platform(hass, component, DOMAIN, {}, config) + + return True + + +class USPSData(object): + """Stores the data retrieved from USPS. + + For each entity to use, acts as the single point responsible for fetching + updates from the server. + """ + + def __init__(self, session, name): + """Initialize the data oject.""" + self.session = session + self.name = name + self.packages = [] + self.mail = [] + self.attribution = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self, **kwargs): + """Fetch the latest info from USPS.""" + import myusps + self.packages = myusps.get_packages(self.session) + self.mail = myusps.get_mail(self.session, now().date()) + self.attribution = myusps.ATTRIBUTION + _LOGGER.debug("Mail, request date: %s, list: %s", + now().date(), self.mail) + _LOGGER.debug("Package list: %s", self.packages) diff --git a/requirements_all.txt b/requirements_all.txt index 0bb785cc0ef..28255e890d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -412,8 +412,8 @@ miniupnpc==1.9 # homeassistant.components.tts mutagen==1.38 -# homeassistant.components.sensor.usps -myusps==1.1.2 +# homeassistant.components.usps +myusps==1.1.3 # homeassistant.components.media_player.nad # homeassistant.components.media_player.nadtcp