2019-04-03 15:40:03 +00:00
|
|
|
"""Support for package tracking sensors from 17track.net."""
|
2022-01-03 18:06:08 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2018-11-08 05:25:08 +00:00
|
|
|
from datetime import timedelta
|
2019-12-01 17:19:17 +00:00
|
|
|
import logging
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-12-01 17:19:17 +00:00
|
|
|
from py17track import Client as SeventeenTrackClient
|
|
|
|
from py17track.errors import SeventeenTrackError
|
2018-11-08 05:25:08 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2022-01-11 15:57:40 +00:00
|
|
|
from homeassistant.components import persistent_notification
|
2021-03-22 18:54:14 +00:00
|
|
|
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
2018-11-08 05:25:08 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_ATTRIBUTION,
|
2021-03-05 18:08:04 +00:00
|
|
|
ATTR_FRIENDLY_NAME,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_LOCATION,
|
|
|
|
CONF_PASSWORD,
|
|
|
|
CONF_SCAN_INTERVAL,
|
|
|
|
CONF_USERNAME,
|
|
|
|
)
|
2022-01-03 18:06:08 +00:00
|
|
|
from homeassistant.core import HomeAssistant
|
2022-05-17 14:40:45 +00:00
|
|
|
from homeassistant.helpers import (
|
|
|
|
aiohttp_client,
|
|
|
|
config_validation as cv,
|
|
|
|
entity_registry as er,
|
|
|
|
)
|
2022-01-03 18:06:08 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
2020-10-05 13:28:15 +00:00
|
|
|
from homeassistant.helpers.event import async_call_later
|
2022-01-03 18:06:08 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
2018-11-08 05:25:08 +00:00
|
|
|
from homeassistant.util import Throttle, slugify
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_DESTINATION_COUNTRY = "destination_country"
|
|
|
|
ATTR_INFO_TEXT = "info_text"
|
2021-03-19 11:34:06 +00:00
|
|
|
ATTR_TIMESTAMP = "timestamp"
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_ORIGIN_COUNTRY = "origin_country"
|
|
|
|
ATTR_PACKAGES = "packages"
|
|
|
|
ATTR_PACKAGE_TYPE = "package_type"
|
|
|
|
ATTR_STATUS = "status"
|
|
|
|
ATTR_TRACKING_INFO_LANGUAGE = "tracking_info_language"
|
|
|
|
ATTR_TRACKING_NUMBER = "tracking_number"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_SHOW_ARCHIVED = "show_archived"
|
|
|
|
CONF_SHOW_DELIVERED = "show_delivered"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DATA_PACKAGES = "package_data"
|
|
|
|
DATA_SUMMARY = "summary_data"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2021-10-08 07:03:09 +00:00
|
|
|
ATTRIBUTION = "Data provided by 17track.net"
|
2018-11-08 05:25:08 +00:00
|
|
|
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
UNIQUE_ID_TEMPLATE = "package_{0}_{1}"
|
|
|
|
ENTITY_ID_TEMPLATE = "sensor.seventeentrack_package_{0}"
|
2019-04-19 07:14:14 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
NOTIFICATION_DELIVERED_ID = "package_delivered_{0}"
|
|
|
|
NOTIFICATION_DELIVERED_TITLE = "Package {0} delivered"
|
|
|
|
NOTIFICATION_DELIVERED_MESSAGE = (
|
2020-01-03 13:47:06 +00:00
|
|
|
"Package Delivered: {0}<br />Visit 17.track for more information: "
|
2019-07-31 19:25:30 +00:00
|
|
|
"https://t.17track.net/track#nums={1}"
|
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
VALUE_DELIVERED = "Delivered"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
|
|
vol.Optional(CONF_SHOW_ARCHIVED, default=False): cv.boolean,
|
|
|
|
vol.Optional(CONF_SHOW_DELIVERED, default=False): cv.boolean,
|
|
|
|
}
|
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
|
2022-01-03 18:06:08 +00:00
|
|
|
async def async_setup_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
config: ConfigType,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
discovery_info: DiscoveryInfoType | None = None,
|
|
|
|
) -> None:
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Configure the platform and add the sensors."""
|
|
|
|
|
2021-03-19 11:34:06 +00:00
|
|
|
session = aiohttp_client.async_get_clientsession(hass)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2021-03-19 11:34:06 +00:00
|
|
|
client = SeventeenTrackClient(session=session)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
login_result = await client.profile.login(
|
2019-07-31 19:25:30 +00:00
|
|
|
config[CONF_USERNAME], config[CONF_PASSWORD]
|
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
if not login_result:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("Invalid username and password provided")
|
2018-11-08 05:25:08 +00:00
|
|
|
return
|
|
|
|
except SeventeenTrackError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("There was an error while logging in: %s", err)
|
2018-11-08 05:25:08 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
scan_interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
data = SeventeenTrackData(
|
|
|
|
client,
|
|
|
|
async_add_entities,
|
|
|
|
scan_interval,
|
|
|
|
config[CONF_SHOW_ARCHIVED],
|
|
|
|
config[CONF_SHOW_DELIVERED],
|
2021-03-19 11:34:06 +00:00
|
|
|
str(hass.config.time_zone),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
await data.async_update()
|
|
|
|
|
|
|
|
|
2021-03-22 18:54:14 +00:00
|
|
|
class SeventeenTrackSummarySensor(SensorEntity):
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Define a summary sensor."""
|
|
|
|
|
2021-10-08 07:03:09 +00:00
|
|
|
_attr_icon = "mdi:package"
|
|
|
|
_attr_native_unit_of_measurement = "packages"
|
|
|
|
|
2018-11-08 05:25:08 +00:00
|
|
|
def __init__(self, data, status, initial_state):
|
|
|
|
"""Initialize."""
|
2021-10-08 07:03:09 +00:00
|
|
|
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
2018-11-08 05:25:08 +00:00
|
|
|
self._data = data
|
|
|
|
self._state = initial_state
|
|
|
|
self._status = status
|
2021-10-08 07:03:09 +00:00
|
|
|
self._attr_name = f"Seventeentrack Packages {status}"
|
|
|
|
self._attr_unique_id = f"summary_{data.account_id}_{slugify(status)}"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return whether the entity is available."""
|
|
|
|
return self._state is not None
|
|
|
|
|
|
|
|
@property
|
2021-08-12 15:40:55 +00:00
|
|
|
def native_value(self):
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Return the state."""
|
|
|
|
return self._state
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Update the sensor."""
|
|
|
|
await self._data.async_update()
|
|
|
|
|
2018-12-12 18:07:06 +00:00
|
|
|
package_data = []
|
2019-07-28 17:55:46 +00:00
|
|
|
for package in self._data.packages.values():
|
2018-12-12 18:07:06 +00:00
|
|
|
if package.status != self._status:
|
|
|
|
continue
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
package_data.append(
|
|
|
|
{
|
|
|
|
ATTR_FRIENDLY_NAME: package.friendly_name,
|
|
|
|
ATTR_INFO_TEXT: package.info_text,
|
2021-03-19 11:34:06 +00:00
|
|
|
ATTR_TIMESTAMP: package.timestamp,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_STATUS: package.status,
|
2020-11-25 20:41:56 +00:00
|
|
|
ATTR_LOCATION: package.location,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_TRACKING_NUMBER: package.tracking_number,
|
|
|
|
}
|
|
|
|
)
|
2018-12-12 18:07:06 +00:00
|
|
|
|
2022-01-19 15:35:20 +00:00
|
|
|
self._attr_extra_state_attributes[ATTR_PACKAGES] = (
|
|
|
|
package_data if package_data else None
|
|
|
|
)
|
2018-12-12 18:07:06 +00:00
|
|
|
|
2018-11-08 05:25:08 +00:00
|
|
|
self._state = self._data.summary.get(self._status)
|
|
|
|
|
|
|
|
|
2021-03-22 18:54:14 +00:00
|
|
|
class SeventeenTrackPackageSensor(SensorEntity):
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Define an individual package sensor."""
|
|
|
|
|
2021-10-08 07:03:09 +00:00
|
|
|
_attr_icon = "mdi:package"
|
|
|
|
|
2018-11-08 05:25:08 +00:00
|
|
|
def __init__(self, data, package):
|
|
|
|
"""Initialize."""
|
2021-10-08 07:03:09 +00:00
|
|
|
self._attr_extra_state_attributes = {
|
|
|
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
2018-11-08 05:25:08 +00:00
|
|
|
ATTR_DESTINATION_COUNTRY: package.destination_country,
|
|
|
|
ATTR_INFO_TEXT: package.info_text,
|
2021-03-19 11:34:06 +00:00
|
|
|
ATTR_TIMESTAMP: package.timestamp,
|
2018-11-08 05:25:08 +00:00
|
|
|
ATTR_LOCATION: package.location,
|
|
|
|
ATTR_ORIGIN_COUNTRY: package.origin_country,
|
|
|
|
ATTR_PACKAGE_TYPE: package.package_type,
|
|
|
|
ATTR_TRACKING_INFO_LANGUAGE: package.tracking_info_language,
|
2018-12-01 04:54:40 +00:00
|
|
|
ATTR_TRACKING_NUMBER: package.tracking_number,
|
2018-11-08 05:25:08 +00:00
|
|
|
}
|
|
|
|
self._data = data
|
2018-12-01 04:54:40 +00:00
|
|
|
self._friendly_name = package.friendly_name
|
2018-11-08 05:25:08 +00:00
|
|
|
self._state = package.status
|
|
|
|
self._tracking_number = package.tracking_number
|
2019-07-28 17:55:46 +00:00
|
|
|
self.entity_id = ENTITY_ID_TEMPLATE.format(self._tracking_number)
|
2021-10-08 07:03:09 +00:00
|
|
|
self._attr_unique_id = UNIQUE_ID_TEMPLATE.format(
|
|
|
|
data.account_id, self._tracking_number
|
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return whether the entity is available."""
|
2019-07-28 17:55:46 +00:00
|
|
|
return self._data.packages.get(self._tracking_number) is not None
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name."""
|
2021-10-17 18:02:42 +00:00
|
|
|
if not (name := self._friendly_name):
|
2018-12-01 04:54:40 +00:00
|
|
|
name = self._tracking_number
|
2019-09-03 19:14:39 +00:00
|
|
|
return f"Seventeentrack Package: {name}"
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
@property
|
2021-08-12 15:40:55 +00:00
|
|
|
def native_value(self):
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Return the state."""
|
|
|
|
return self._state
|
|
|
|
|
|
|
|
async def async_update(self):
|
|
|
|
"""Update the sensor."""
|
|
|
|
await self._data.async_update()
|
|
|
|
|
2019-07-28 17:55:46 +00:00
|
|
|
if not self.available:
|
2020-10-05 13:28:15 +00:00
|
|
|
# Entity cannot be removed while its being added
|
|
|
|
async_call_later(self.hass, 1, self._remove)
|
2018-11-08 05:25:08 +00:00
|
|
|
return
|
|
|
|
|
2019-07-28 17:55:46 +00:00
|
|
|
package = self._data.packages.get(self._tracking_number, None)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
# If the user has elected to not see delivered packages and one gets
|
2019-04-19 07:14:14 +00:00
|
|
|
# delivered, post a notification:
|
2018-11-08 05:25:08 +00:00
|
|
|
if package.status == VALUE_DELIVERED and not self._data.show_delivered:
|
2019-07-28 17:55:46 +00:00
|
|
|
self._notify_delivered()
|
2020-10-05 13:28:15 +00:00
|
|
|
# Entity cannot be removed while its being added
|
|
|
|
async_call_later(self.hass, 1, self._remove)
|
2018-11-08 05:25:08 +00:00
|
|
|
return
|
|
|
|
|
2021-10-08 07:03:09 +00:00
|
|
|
self._attr_extra_state_attributes.update(
|
2021-03-19 11:34:06 +00:00
|
|
|
{
|
|
|
|
ATTR_INFO_TEXT: package.info_text,
|
|
|
|
ATTR_TIMESTAMP: package.timestamp,
|
|
|
|
ATTR_LOCATION: package.location,
|
|
|
|
}
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
self._state = package.status
|
2019-07-28 17:55:46 +00:00
|
|
|
self._friendly_name = package.friendly_name
|
|
|
|
|
2020-10-05 13:28:15 +00:00
|
|
|
async def _remove(self, *_):
|
2019-07-28 17:55:46 +00:00
|
|
|
"""Remove entity itself."""
|
2021-02-08 09:45:46 +00:00
|
|
|
await self.async_remove(force_remove=True)
|
2019-07-28 17:55:46 +00:00
|
|
|
|
2022-05-17 14:40:45 +00:00
|
|
|
reg = er.async_get(self.hass)
|
2019-07-28 17:55:46 +00:00
|
|
|
entity_id = reg.async_get_entity_id(
|
2019-07-31 19:25:30 +00:00
|
|
|
"sensor",
|
|
|
|
"seventeentrack",
|
|
|
|
UNIQUE_ID_TEMPLATE.format(self._data.account_id, self._tracking_number),
|
|
|
|
)
|
2019-07-28 17:55:46 +00:00
|
|
|
if entity_id:
|
|
|
|
reg.async_remove(entity_id)
|
|
|
|
|
|
|
|
def _notify_delivered(self):
|
|
|
|
"""Notify when package is delivered."""
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.info("Package delivered: %s", self._tracking_number)
|
|
|
|
|
|
|
|
identification = (
|
|
|
|
self._friendly_name if self._friendly_name else self._tracking_number
|
|
|
|
)
|
|
|
|
message = NOTIFICATION_DELIVERED_MESSAGE.format(
|
2020-08-07 20:14:42 +00:00
|
|
|
identification, self._tracking_number
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-07-28 17:55:46 +00:00
|
|
|
title = NOTIFICATION_DELIVERED_TITLE.format(identification)
|
2019-07-31 19:25:30 +00:00
|
|
|
notification_id = NOTIFICATION_DELIVERED_TITLE.format(self._tracking_number)
|
2019-07-28 17:55:46 +00:00
|
|
|
|
2022-01-11 15:57:40 +00:00
|
|
|
persistent_notification.create(
|
|
|
|
self.hass, message, title=title, notification_id=notification_id
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SeventeenTrackData:
|
|
|
|
"""Define a data handler for 17track.net."""
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
def __init__(
|
2021-03-19 11:34:06 +00:00
|
|
|
self,
|
|
|
|
client,
|
|
|
|
async_add_entities,
|
|
|
|
scan_interval,
|
|
|
|
show_archived,
|
|
|
|
show_delivered,
|
|
|
|
timezone,
|
2019-07-31 19:25:30 +00:00
|
|
|
):
|
2018-11-08 05:25:08 +00:00
|
|
|
"""Initialize."""
|
|
|
|
self._async_add_entities = async_add_entities
|
|
|
|
self._client = client
|
|
|
|
self._scan_interval = scan_interval
|
|
|
|
self._show_archived = show_archived
|
|
|
|
self.account_id = client.profile.account_id
|
2019-07-28 17:55:46 +00:00
|
|
|
self.packages = {}
|
2018-11-08 05:25:08 +00:00
|
|
|
self.show_delivered = show_delivered
|
2021-03-19 11:34:06 +00:00
|
|
|
self.timezone = timezone
|
2018-11-08 05:25:08 +00:00
|
|
|
self.summary = {}
|
|
|
|
|
|
|
|
self.async_update = Throttle(self._scan_interval)(self._async_update)
|
2019-07-28 17:55:46 +00:00
|
|
|
self.first_update = True
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
async def _async_update(self):
|
|
|
|
"""Get updated data from 17track.net."""
|
|
|
|
|
|
|
|
try:
|
|
|
|
packages = await self._client.profile.packages(
|
2021-03-19 11:34:06 +00:00
|
|
|
show_archived=self._show_archived, tz=self.timezone
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
|
|
|
_LOGGER.debug("New package data received: %s", packages)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-28 17:55:46 +00:00
|
|
|
new_packages = {p.tracking_number: p for p in packages}
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-28 17:55:46 +00:00
|
|
|
to_add = set(new_packages) - set(self.packages)
|
2019-07-17 19:05:26 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug("Will add new tracking numbers: %s", to_add)
|
2019-07-28 17:55:46 +00:00
|
|
|
if to_add:
|
2019-07-31 19:25:30 +00:00
|
|
|
self._async_add_entities(
|
|
|
|
[
|
|
|
|
SeventeenTrackPackageSensor(self, new_packages[tracking_number])
|
|
|
|
for tracking_number in to_add
|
|
|
|
],
|
|
|
|
True,
|
|
|
|
)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
2019-07-28 17:55:46 +00:00
|
|
|
self.packages = new_packages
|
2018-11-08 05:25:08 +00:00
|
|
|
except SeventeenTrackError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("There was an error retrieving packages: %s", err)
|
2018-11-08 05:25:08 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.summary = await self._client.profile.summary(
|
2019-07-31 19:25:30 +00:00
|
|
|
show_archived=self._show_archived
|
|
|
|
)
|
|
|
|
_LOGGER.debug("New summary data received: %s", self.summary)
|
2019-07-28 17:55:46 +00:00
|
|
|
|
|
|
|
# creating summary sensors on first update
|
|
|
|
if self.first_update:
|
|
|
|
self.first_update = False
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self._async_add_entities(
|
|
|
|
[
|
|
|
|
SeventeenTrackSummarySensor(self, status, quantity)
|
|
|
|
for status, quantity in self.summary.items()
|
|
|
|
],
|
|
|
|
True,
|
|
|
|
)
|
2019-07-28 17:55:46 +00:00
|
|
|
|
2018-11-08 05:25:08 +00:00
|
|
|
except SeventeenTrackError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error("There was an error retrieving the summary: %s", err)
|
2018-11-08 05:25:08 +00:00
|
|
|
self.summary = {}
|