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