Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for non-delivered packages recorded in AfterShip."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any, Final
7 
8 from pyaftership import AfterShip, AfterShipException
9 
10 from homeassistant.components.sensor import SensorEntity
11 from homeassistant.core import HomeAssistant, ServiceCall
14  async_dispatcher_connect,
15  async_dispatcher_send,
16 )
17 from homeassistant.helpers.entity_platform import AddEntitiesCallback
18 from homeassistant.util import Throttle
19 
20 from . import AfterShipConfigEntry
21 from .const import (
22  ADD_TRACKING_SERVICE_SCHEMA,
23  ATTR_TRACKINGS,
24  ATTRIBUTION,
25  BASE,
26  CONF_SLUG,
27  CONF_TITLE,
28  CONF_TRACKING_NUMBER,
29  DOMAIN,
30  MIN_TIME_BETWEEN_UPDATES,
31  REMOVE_TRACKING_SERVICE_SCHEMA,
32  SERVICE_ADD_TRACKING,
33  SERVICE_REMOVE_TRACKING,
34  UPDATE_TOPIC,
35 )
36 
37 _LOGGER: Final = logging.getLogger(__name__)
38 
39 PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False)
40 
41 
43  hass: HomeAssistant,
44  config_entry: AfterShipConfigEntry,
45  async_add_entities: AddEntitiesCallback,
46 ) -> None:
47  """Set up AfterShip sensor entities based on a config entry."""
48  aftership = config_entry.runtime_data
49 
50  async_add_entities([AfterShipSensor(aftership, config_entry.title)], True)
51 
52  async def handle_add_tracking(call: ServiceCall) -> None:
53  """Call when a user adds a new Aftership tracking from Home Assistant."""
54  await aftership.trackings.add(
55  tracking_number=call.data[CONF_TRACKING_NUMBER],
56  title=call.data.get(CONF_TITLE),
57  slug=call.data.get(CONF_SLUG),
58  )
59  async_dispatcher_send(hass, UPDATE_TOPIC)
60 
61  hass.services.async_register(
62  DOMAIN,
63  SERVICE_ADD_TRACKING,
64  handle_add_tracking,
65  schema=ADD_TRACKING_SERVICE_SCHEMA,
66  )
67 
68  async def handle_remove_tracking(call: ServiceCall) -> None:
69  """Call when a user removes an Aftership tracking from Home Assistant."""
70  await aftership.trackings.remove(
71  tracking_number=call.data[CONF_TRACKING_NUMBER],
72  slug=call.data[CONF_SLUG],
73  )
74  async_dispatcher_send(hass, UPDATE_TOPIC)
75 
76  hass.services.async_register(
77  DOMAIN,
78  SERVICE_REMOVE_TRACKING,
79  handle_remove_tracking,
80  schema=REMOVE_TRACKING_SERVICE_SCHEMA,
81  )
82 
83 
85  """Representation of a AfterShip sensor."""
86 
87  _attr_attribution = ATTRIBUTION
88  _attr_native_unit_of_measurement: str = "packages"
89  _attr_translation_key = "packages"
90 
91  def __init__(self, aftership: AfterShip, name: str) -> None:
92  """Initialize the sensor."""
93  self._attributes_attributes: dict[str, Any] = {}
94  self._state_state: int | None = None
95  self.aftershipaftership = aftership
96  self._attr_name_attr_name = name
97 
98  @property
99  def native_value(self) -> int | None:
100  """Return the state of the sensor."""
101  return self._state_state
102 
103  @property
104  def extra_state_attributes(self) -> dict[str, str]:
105  """Return attributes for the sensor."""
106  return self._attributes_attributes
107 
108  async def async_added_to_hass(self) -> None:
109  """Register callbacks."""
110  self.async_on_removeasync_on_remove(
111  async_dispatcher_connect(self.hasshass, UPDATE_TOPIC, self._force_update_force_update)
112  )
113 
114  async def _force_update(self) -> None:
115  """Force update of data."""
116  await self.async_updateasync_update(no_throttle=True)
117  self.async_write_ha_stateasync_write_ha_state()
118 
119  @Throttle(MIN_TIME_BETWEEN_UPDATES)
120  async def async_update(self, **kwargs: Any) -> None:
121  """Get the latest data from the AfterShip API."""
122  try:
123  trackings = await self.aftershipaftership.trackings.list()
124  except AfterShipException as err:
125  _LOGGER.error("Errors when querying AfterShip - %s", err)
126  return
127 
128  status_to_ignore = {"delivered"}
129  status_counts: dict[str, int] = {}
130  parsed_trackings = []
131  not_delivered_count = 0
132 
133  for track in trackings["trackings"]:
134  status = track["tag"].lower()
135  name = (
136  track["tracking_number"] if track["title"] is None else track["title"]
137  )
138  last_checkpoint = (
139  f"Shipment {track['tag'].lower()}"
140  if not track["checkpoints"]
141  else track["checkpoints"][-1]
142  )
143  status_counts[status] = status_counts.get(status, 0) + 1
144  parsed_trackings.append(
145  {
146  "name": name,
147  "tracking_number": track["tracking_number"],
148  "slug": track["slug"],
149  "link": f"{BASE}{track['slug']}/{track['tracking_number']}",
150  "last_update": track["updated_at"],
151  "expected_delivery": track["expected_delivery"],
152  "status": track["tag"],
153  "last_checkpoint": last_checkpoint,
154  }
155  )
156 
157  if status not in status_to_ignore:
158  not_delivered_count += 1
159  else:
160  _LOGGER.debug("Ignoring %s as it has status: %s", name, status)
161 
162  self._attributes_attributes = {
163  **status_counts,
164  ATTR_TRACKINGS: parsed_trackings,
165  }
166 
167  self._state_state = not_delivered_count
None __init__(self, AfterShip aftership, str name)
Definition: sensor.py:91
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, AfterShipConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:46
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193