Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for departure information for public transport in Munich."""
2 
3 # mypy: ignore-errors
4 from __future__ import annotations
5 
6 from copy import deepcopy
7 from datetime import timedelta
8 import logging
9 
10 import MVGLive
11 import voluptuous as vol
12 
14  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
15  SensorEntity,
16 )
17 from homeassistant.const import CONF_NAME, UnitOfTime
18 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 CONF_NEXT_DEPARTURE = "nextdeparture"
26 
27 CONF_STATION = "station"
28 CONF_DESTINATIONS = "destinations"
29 CONF_DIRECTIONS = "directions"
30 CONF_LINES = "lines"
31 CONF_PRODUCTS = "products"
32 CONF_TIMEOFFSET = "timeoffset"
33 CONF_NUMBER = "number"
34 
35 DEFAULT_PRODUCT = ["U-Bahn", "Tram", "Bus", "ExpressBus", "S-Bahn", "Nachteule"]
36 
37 ICONS = {
38  "U-Bahn": "mdi:subway",
39  "Tram": "mdi:tram",
40  "Bus": "mdi:bus",
41  "ExpressBus": "mdi:bus",
42  "S-Bahn": "mdi:train",
43  "Nachteule": "mdi:owl",
44  "SEV": "mdi:checkbox-blank-circle-outline",
45  "-": "mdi:clock",
46 }
47 ATTRIBUTION = "Data provided by MVG-live.de"
48 
49 SCAN_INTERVAL = timedelta(seconds=30)
50 
51 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
52  {
53  vol.Required(CONF_NEXT_DEPARTURE): [
54  {
55  vol.Required(CONF_STATION): cv.string,
56  vol.Optional(CONF_DESTINATIONS, default=[""]): cv.ensure_list_csv,
57  vol.Optional(CONF_DIRECTIONS, default=[""]): cv.ensure_list_csv,
58  vol.Optional(CONF_LINES, default=[""]): cv.ensure_list_csv,
59  vol.Optional(
60  CONF_PRODUCTS, default=DEFAULT_PRODUCT
61  ): cv.ensure_list_csv,
62  vol.Optional(CONF_TIMEOFFSET, default=0): cv.positive_int,
63  vol.Optional(CONF_NUMBER, default=1): cv.positive_int,
64  vol.Optional(CONF_NAME): cv.string,
65  }
66  ]
67  }
68 )
69 
70 
72  hass: HomeAssistant,
73  config: ConfigType,
74  add_entities: AddEntitiesCallback,
75  discovery_info: DiscoveryInfoType | None = None,
76 ) -> None:
77  """Set up the MVGLive sensor."""
79  (
81  nextdeparture.get(CONF_STATION),
82  nextdeparture.get(CONF_DESTINATIONS),
83  nextdeparture.get(CONF_DIRECTIONS),
84  nextdeparture.get(CONF_LINES),
85  nextdeparture.get(CONF_PRODUCTS),
86  nextdeparture.get(CONF_TIMEOFFSET),
87  nextdeparture.get(CONF_NUMBER),
88  nextdeparture.get(CONF_NAME),
89  )
90  for nextdeparture in config[CONF_NEXT_DEPARTURE]
91  ),
92  True,
93  )
94 
95 
97  """Implementation of an MVG Live sensor."""
98 
99  _attr_attribution = ATTRIBUTION
100 
101  def __init__(
102  self,
103  station,
104  destinations,
105  directions,
106  lines,
107  products,
108  timeoffset,
109  number,
110  name,
111  ):
112  """Initialize the sensor."""
113  self._station_station = station
114  self._name_name = name
115  self.datadata = MVGLiveData(
116  station, destinations, directions, lines, products, timeoffset, number
117  )
118  self._state_state = None
119  self._icon_icon = ICONS["-"]
120 
121  @property
122  def name(self):
123  """Return the name of the sensor."""
124  if self._name_name:
125  return self._name_name
126  return self._station_station
127 
128  @property
129  def native_value(self):
130  """Return the next departure time."""
131  return self._state_state
132 
133  @property
135  """Return the state attributes."""
136  if not (dep := self.datadata.departures):
137  return None
138  attr = dep[0] # next depature attributes
139  attr["departures"] = deepcopy(dep) # all departures dictionary
140  return attr
141 
142  @property
143  def icon(self):
144  """Icon to use in the frontend, if any."""
145  return self._icon_icon
146 
147  @property
149  """Return the unit this state is expressed in."""
150  return UnitOfTime.MINUTES
151 
152  def update(self) -> None:
153  """Get the latest data and update the state."""
154  self.datadata.update()
155  if not self.datadata.departures:
156  self._state_state = "-"
157  self._icon_icon = ICONS["-"]
158  else:
159  self._state_state = self.datadata.departures[0].get("time", "-")
160  self._icon_icon = ICONS[self.datadata.departures[0].get("product", "-")]
161 
162 
164  """Pull data from the mvg-live.de web page."""
165 
166  def __init__(
167  self, station, destinations, directions, lines, products, timeoffset, number
168  ):
169  """Initialize the sensor."""
170  self._station_station = station
171  self._destinations_destinations = destinations
172  self._directions_directions = directions
173  self._lines_lines = lines
174  self._products_products = products
175  self._timeoffset_timeoffset = timeoffset
176  self._number_number = number
177  self._include_ubahn_include_ubahn = "U-Bahn" in self._products_products
178  self._include_tram_include_tram = "Tram" in self._products_products
179  self._include_bus_include_bus = "Bus" in self._products_products
180  self._include_sbahn_include_sbahn = "S-Bahn" in self._products_products
181  self.mvgmvg = MVGLive.MVGLive()
182  self.departuresdepartures = []
183 
184  def update(self):
185  """Update the connection data."""
186  try:
187  _departures = self.mvgmvg.getlivedata(
188  station=self._station_station,
189  timeoffset=self._timeoffset_timeoffset,
190  ubahn=self._include_ubahn_include_ubahn,
191  tram=self._include_tram_include_tram,
192  bus=self._include_bus_include_bus,
193  sbahn=self._include_sbahn_include_sbahn,
194  )
195  except ValueError:
196  self.departuresdepartures = []
197  _LOGGER.warning("Returned data not understood")
198  return
199  self.departuresdepartures = []
200  for i, _departure in enumerate(_departures):
201  # find the first departure meeting the criteria
202  if (
203  "" not in self._destinations_destinations[:1]
204  and _departure["destination"] not in self._destinations_destinations
205  ):
206  continue
207 
208  if (
209  "" not in self._directions_directions[:1]
210  and _departure["direction"] not in self._directions_directions
211  ):
212  continue
213 
214  if "" not in self._lines_lines[:1] and _departure["linename"] not in self._lines_lines:
215  continue
216 
217  if _departure["time"] < self._timeoffset_timeoffset:
218  continue
219 
220  # now select the relevant data
221  _nextdep = {}
222  for k in ("destination", "linename", "time", "direction", "product"):
223  _nextdep[k] = _departure.get(k, "")
224  _nextdep["time"] = int(_nextdep["time"])
225  self.departuresdepartures.append(_nextdep)
226  if i == self._number_number - 1:
227  break
def __init__(self, station, destinations, directions, lines, products, timeoffset, number)
Definition: sensor.py:168
def __init__(self, station, destinations, directions, lines, products, timeoffset, number, name)
Definition: sensor.py:111
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
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:76