Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Västtrafik public transport."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime, timedelta
6 import logging
7 
8 import vasttrafik
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
13  SensorEntity,
14 )
15 from homeassistant.const import CONF_DELAY, CONF_NAME
16 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers.entity_platform import AddEntitiesCallback
19 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
20 from homeassistant.util import Throttle
21 from homeassistant.util.dt import now
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 ATTR_ACCESSIBILITY = "accessibility"
26 ATTR_DIRECTION = "direction"
27 ATTR_LINE = "line"
28 ATTR_TRACK = "track"
29 ATTR_FROM = "from"
30 ATTR_TO = "to"
31 ATTR_DELAY = "delay"
32 
33 CONF_DEPARTURES = "departures"
34 CONF_FROM = "from"
35 CONF_HEADING = "heading"
36 CONF_LINES = "lines"
37 CONF_KEY = "key"
38 CONF_SECRET = "secret"
39 
40 DEFAULT_DELAY = 0
41 
42 MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
43 
44 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
45  {
46  vol.Required(CONF_KEY): cv.string,
47  vol.Required(CONF_SECRET): cv.string,
48  vol.Required(CONF_DEPARTURES): [
49  {
50  vol.Required(CONF_FROM): cv.string,
51  vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int,
52  vol.Optional(CONF_HEADING): cv.string,
53  vol.Optional(CONF_LINES, default=[]): vol.All(
54  cv.ensure_list, [cv.string]
55  ),
56  vol.Optional(CONF_NAME): cv.string,
57  }
58  ],
59  }
60 )
61 
62 
64  hass: HomeAssistant,
65  config: ConfigType,
66  add_entities: AddEntitiesCallback,
67  discovery_info: DiscoveryInfoType | None = None,
68 ) -> None:
69  """Set up the departure sensor."""
70  planner = vasttrafik.JournyPlanner(config.get(CONF_KEY), config.get(CONF_SECRET))
72  (
74  planner,
75  departure.get(CONF_NAME),
76  departure.get(CONF_FROM),
77  departure.get(CONF_HEADING),
78  departure.get(CONF_LINES),
79  departure.get(CONF_DELAY),
80  )
81  for departure in config[CONF_DEPARTURES]
82  ),
83  True,
84  )
85 
86 
88  """Implementation of a Vasttrafik Departure Sensor."""
89 
90  _attr_attribution = "Data provided by Västtrafik"
91  _attr_icon = "mdi:train"
92 
93  def __init__(self, planner, name, departure, heading, lines, delay):
94  """Initialize the sensor."""
95  self._planner_planner = planner
96  self._name_name = name or departure
97  self._departure_departure = self.get_station_idget_station_id(departure)
98  self._heading_heading = self.get_station_idget_station_id(heading) if heading else None
99  self._lines_lines = lines if lines else None
100  self._delay_delay = timedelta(minutes=delay)
101  self._departureboard_departureboard = None
102  self._state_state = None
103  self._attributes_attributes = None
104 
105  def get_station_id(self, location):
106  """Get the station ID."""
107  if location.isdecimal():
108  station_info = {"station_name": location, "station_id": location}
109  else:
110  station_id = self._planner_planner.location_name(location)[0]["gid"]
111  station_info = {"station_name": location, "station_id": station_id}
112  return station_info
113 
114  @property
115  def name(self):
116  """Return the name of the sensor."""
117  return self._name_name
118 
119  @property
121  """Return the state attributes."""
122  return self._attributes_attributes
123 
124  @property
125  def native_value(self):
126  """Return the next departure time."""
127  return self._state_state
128 
129  @Throttle(MIN_TIME_BETWEEN_UPDATES)
130  def update(self) -> None:
131  """Get the departure board."""
132  try:
133  self._departureboard_departureboard = self._planner_planner.departureboard(
134  self._departure_departure["station_id"],
135  direction=self._heading_heading["station_id"] if self._heading_heading else None,
136  date=now() + self._delay_delay,
137  )
138  except vasttrafik.Error:
139  _LOGGER.debug("Unable to read departure board, updating token")
140  self._planner_planner.update_token()
141 
142  if not self._departureboard_departureboard:
143  _LOGGER.debug(
144  "No departures from departure station %s to destination station %s",
145  self._departure_departure["station_name"],
146  self._heading_heading["station_name"] if self._heading_heading else "ANY",
147  )
148  self._state_state = None
149  self._attributes_attributes = {}
150  else:
151  for departure in self._departureboard_departureboard:
152  service_journey = departure.get("serviceJourney", {})
153  line = service_journey.get("line", {})
154 
155  if departure.get("isCancelled"):
156  continue
157  if not self._lines_lines or line.get("shortName") in self._lines_lines:
158  if "estimatedOtherwisePlannedTime" in departure:
159  try:
160  self._state_state = datetime.fromisoformat(
161  departure["estimatedOtherwisePlannedTime"]
162  ).strftime("%H:%M")
163  except ValueError:
164  self._state_state = departure["estimatedOtherwisePlannedTime"]
165  else:
166  self._state_state = None
167 
168  stop_point = departure.get("stopPoint", {})
169 
170  params = {
171  ATTR_ACCESSIBILITY: "wheelChair"
172  if line.get("isWheelchairAccessible")
173  else None,
174  ATTR_DIRECTION: service_journey.get("direction"),
175  ATTR_LINE: line.get("shortName"),
176  ATTR_TRACK: stop_point.get("platform"),
177  ATTR_FROM: stop_point.get("name"),
178  ATTR_TO: self._heading_heading["station_name"]
179  if self._heading_heading
180  else "ANY",
181  ATTR_DELAY: self._delay_delay.seconds // 60 % 60,
182  }
183 
184  self._attributes_attributes = {k: v for k, v in params.items() if v}
185  break
def __init__(self, planner, name, departure, heading, lines, delay)
Definition: sensor.py:93
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:68
datetime now(HomeAssistant hass)
Definition: template.py:1890