Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for the Italian train system using ViaggiaTreno API."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from http import HTTPStatus
7 import logging
8 import time
9 
10 import aiohttp
11 import voluptuous as vol
12 
14  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
15  SensorEntity,
16 )
17 from homeassistant.const import UnitOfTime
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.aiohttp_client import async_get_clientsession
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 VIAGGIATRENO_ENDPOINT = (
27  "http://www.viaggiatreno.it/infomobilita/"
28  "resteasy/viaggiatreno/andamentoTreno/"
29  "{station_id}/{train_id}/{timestamp}"
30 )
31 
32 REQUEST_TIMEOUT = 5 # seconds
33 ICON = "mdi:train"
34 MONITORED_INFO = [
35  "categoria",
36  "compOrarioArrivoZeroEffettivo",
37  "compOrarioPartenzaZeroEffettivo",
38  "destinazione",
39  "numeroTreno",
40  "orarioArrivo",
41  "orarioPartenza",
42  "origine",
43  "subTitle",
44 ]
45 
46 DEFAULT_NAME = "Train {}"
47 
48 CONF_NAME = "train_name"
49 CONF_STATION_ID = "station_id"
50 CONF_STATION_NAME = "station_name"
51 CONF_TRAIN_ID = "train_id"
52 
53 ARRIVED_STRING = "Arrived"
54 CANCELLED_STRING = "Cancelled"
55 NOT_DEPARTED_STRING = "Not departed yet"
56 NO_INFORMATION_STRING = "No information for this train now"
57 
58 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
59  {
60  vol.Required(CONF_TRAIN_ID): cv.string,
61  vol.Required(CONF_STATION_ID): cv.string,
62  vol.Optional(CONF_NAME): cv.string,
63  }
64 )
65 
66 
68  hass: HomeAssistant,
69  config: ConfigType,
70  async_add_entities: AddEntitiesCallback,
71  discovery_info: DiscoveryInfoType | None = None,
72 ) -> None:
73  """Set up the ViaggiaTreno platform."""
74  train_id = config.get(CONF_TRAIN_ID)
75  station_id = config.get(CONF_STATION_ID)
76  if not (name := config.get(CONF_NAME)):
77  name = DEFAULT_NAME.format(train_id)
78  async_add_entities([ViaggiaTrenoSensor(train_id, station_id, name)])
79 
80 
81 async def async_http_request(hass, uri):
82  """Perform actual request."""
83  try:
84  session = async_get_clientsession(hass)
85  async with asyncio.timeout(REQUEST_TIMEOUT):
86  req = await session.get(uri)
87  if req.status != HTTPStatus.OK:
88  return {"error": req.status}
89  json_response = await req.json()
90  except (TimeoutError, aiohttp.ClientError) as exc:
91  _LOGGER.error("Cannot connect to ViaggiaTreno API endpoint: %s", exc)
92  return None
93  except ValueError:
94  _LOGGER.error("Received non-JSON data from ViaggiaTreno API endpoint")
95  return None
96  return json_response
97 
98 
100  """Implementation of a ViaggiaTreno sensor."""
101 
102  _attr_attribution = "Powered by ViaggiaTreno Data"
103 
104  def __init__(self, train_id, station_id, name):
105  """Initialize the sensor."""
106  self._state_state = None
107  self._attributes_attributes = {}
108  self._unit_unit = ""
109  self._icon_icon = ICON
110  self._station_id_station_id = station_id
111  self._name_name = name
112 
113  self.uriuri = VIAGGIATRENO_ENDPOINT.format(
114  station_id=station_id, train_id=train_id, timestamp=int(time.time()) * 1000
115  )
116 
117  @property
118  def name(self):
119  """Return the name of the sensor."""
120  return self._name_name
121 
122  @property
123  def native_value(self):
124  """Return the state of the sensor."""
125  return self._state_state
126 
127  @property
128  def icon(self):
129  """Icon to use in the frontend, if any."""
130  return self._icon_icon
131 
132  @property
134  """Return the unit of measurement."""
135  return self._unit_unit
136 
137  @property
139  """Return extra attributes."""
140  return self._attributes_attributes
141 
142  @staticmethod
143  def has_departed(data):
144  """Check if the train has actually departed."""
145  try:
146  first_station = data["fermate"][0]
147  if data["oraUltimoRilevamento"] or first_station["effettiva"]:
148  return True
149  except ValueError:
150  _LOGGER.error("Cannot fetch first station: %s", data)
151  return False
152 
153  @staticmethod
154  def has_arrived(data):
155  """Check if the train has already arrived."""
156  last_station = data["fermate"][-1]
157  if not last_station["effettiva"]:
158  return False
159  return True
160 
161  @staticmethod
162  def is_cancelled(data):
163  """Check if the train is cancelled."""
164  if data["tipoTreno"] == "ST" and data["provvedimento"] == 1:
165  return True
166  return False
167 
168  async def async_update(self) -> None:
169  """Update state."""
170  uri = self.uriuri
171  res = await async_http_request(self.hasshass, uri)
172  if res.get("error", ""):
173  if res["error"] == 204:
174  self._state_state = NO_INFORMATION_STRING
175  self._unit_unit = ""
176  else:
177  self._state_state = f"Error: {res['error']}"
178  self._unit_unit = ""
179  else:
180  for i in MONITORED_INFO:
181  self._attributes_attributes[i] = res[i]
182 
183  if self.is_cancelledis_cancelled(res):
184  self._state_state = CANCELLED_STRING
185  self._icon_icon = "mdi:cancel"
186  self._unit_unit = ""
187  elif not self.has_departedhas_departed(res):
188  self._state_state = NOT_DEPARTED_STRING
189  self._unit_unit = ""
190  elif self.has_arrivedhas_arrived(res):
191  self._state_state = ARRIVED_STRING
192  self._unit_unit = ""
193  else:
194  self._state_state = res.get("ritardo")
195  self._unit_unit = UnitOfTime.MINUTES
196  self._icon_icon = ICON
def __init__(self, train_id, station_id, name)
Definition: sensor.py:104
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:72
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)