From b32e6fe0d5cb15bf32adb3e72d5174b609f3a61a Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen <max.andersen@gmail.com> Date: Thu, 27 Dec 2018 19:01:57 +0100 Subject: [PATCH] Add AfterShip sensor for packages (#18034) * Add AfterShip sensor for packages Why: * I receive a lot of packages from many different shipping companies. * I would like to see in haas how many packages are being delivered. This change addreses the need by: * Adding a sensor for AfterShip (aftership.com) * AfterShip supports ~490 couriers world wide thus should cover almost any sensible tracking. Notes: - For now this sensor assumes you somehow have added trackings to aftership manually. - Future idea is to expose service that allows adding a tracking based on incoming mails. - Other improvments would be to add map markers for package locations. Related: - https://community.home-assistant.io/t/package-tracking/858 - https://community.home-assistant.io/t/aftership-package-tracking/24068 - https://community.home-assistant.io/t/aftership-shipment-tracking-platform/14074 - https://community.home-assistant.io/t/aftership-state-card/57912 * Fix typo and update ordering --- .coveragerc | 1 + homeassistant/components/sensor/aftership.py | 128 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 132 insertions(+) create mode 100644 homeassistant/components/sensor/aftership.py diff --git a/.coveragerc b/.coveragerc index ff5bb876b8a..d1125c59146 100644 --- a/.coveragerc +++ b/.coveragerc @@ -693,6 +693,7 @@ omit = homeassistant/components/route53.py homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/scene/lifx_cloud.py + homeassistant/components/sensor/aftership.py homeassistant/components/sensor/airvisual.py homeassistant/components/sensor/alpha_vantage.py homeassistant/components/sensor/ambient_station.py diff --git a/homeassistant/components/sensor/aftership.py b/homeassistant/components/sensor/aftership.py new file mode 100644 index 00000000000..eb5188a95cb --- /dev/null +++ b/homeassistant/components/sensor/aftership.py @@ -0,0 +1,128 @@ +""" +Support for non-delivered packages recorded in AfterShip. + +For more details about this platform, please refer to the documentation at +https://www.home-assistant.io/components/sensor.aftership/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle + +REQUIREMENTS = ['pyaftership==0.1.2'] + +_LOGGER = logging.getLogger(__name__) + +ATTRIBUTION = 'Information provided by AfterShip' + +CONF_SLUG = 'slug' +CONF_TITLE = 'title' +CONF_TRACKING_NUMBER = 'tracking_number' + +DEFAULT_NAME = 'aftership' + +ICON = 'mdi:package-variant-closed' + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +async def async_setup_platform( + hass, config, async_add_entities, discovery_info=None): + """Set up the AfterShip sensor platform.""" + from pyaftership.tracker import Tracking + + apikey = config[CONF_API_KEY] + name = config[CONF_NAME] + + session = async_get_clientsession(hass) + aftership = Tracking(hass.loop, session, apikey) + + await aftership.get_trackings() + + if not aftership.meta or aftership.meta['code'] != 200: + _LOGGER.error("No tracking data found. Check API key is correct: %s", + aftership.meta) + return + + async_add_entities([AfterShipSensor(aftership, name)], True) + + +class AfterShipSensor(Entity): + """Representation of a AfterShip sensor.""" + + def __init__(self, aftership, name): + """Initialize the sensor.""" + self._attributes = {} + self._name = name + self._state = None + self.aftership = aftership + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return 'packages' + + @property + def device_state_attributes(self): + """Return attributes for the sensor.""" + return self._attributes + + @property + def icon(self): + """Icon to use in the frontend.""" + return ICON + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + async def async_update(self): + """Get the latest data from the AfterShip API.""" + await self.aftership.get_trackings() + + if not self.aftership.meta: + _LOGGER.error("Unknown errors when querying") + return + if self.aftership.meta['code'] != 200: + _LOGGER.error( + "Errors when querying AfterShip. %s", str(self.aftership.meta)) + return + + status_to_ignore = {'delivered'} + status_counts = {} + not_delivered_count = 0 + + for tracking in self.aftership.trackings['trackings']: + status = tracking['tag'].lower() + name = tracking['tracking_number'] + status_counts[status] = status_counts.get(status, 0)+1 + if status not in status_to_ignore: + not_delivered_count += 1 + else: + _LOGGER.debug("Ignoring %s as it has status: %s", name, status) + + self._attributes = { + ATTR_ATTRIBUTION: ATTRIBUTION, + **status_counts + } + + self._state = not_delivered_count diff --git a/requirements_all.txt b/requirements_all.txt index ec85e22da17..412b274b08f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -881,6 +881,9 @@ pyW800rf32==0.1 # homeassistant.components.ads pyads==2.2.6 +# homeassistant.components.sensor.aftership +pyaftership==0.1.2 + # homeassistant.components.sensor.airvisual pyairvisual==2.0.1 -- GitLab