From b2ab4443a7ab576b6c882861211c52a60dfe7509 Mon Sep 17 00:00:00 2001 From: Fabrizio Furnari Date: Thu, 16 Nov 2017 07:07:16 +0100 Subject: [PATCH] New sensor viaggiatreno. (#10522) * New sensor viaggiatreno. I've messed up the previous PR so here it is in a new one. Should include also all corrections from @pvizeli * fixes from PR 10522 * fixed import order * requested changes from MartinHjelmare --- .coveragerc | 1 + .../components/sensor/viaggiatreno.py | 187 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 homeassistant/components/sensor/viaggiatreno.py diff --git a/.coveragerc b/.coveragerc index 4ff7fa24102..01187b92d66 100644 --- a/.coveragerc +++ b/.coveragerc @@ -583,6 +583,7 @@ omit = homeassistant/components/sensor/upnp.py homeassistant/components/sensor/ups.py homeassistant/components/sensor/vasttrafik.py + homeassistant/components/sensor/viaggiatreno.py homeassistant/components/sensor/waqi.py homeassistant/components/sensor/whois.py homeassistant/components/sensor/worldtidesinfo.py diff --git a/homeassistant/components/sensor/viaggiatreno.py b/homeassistant/components/sensor/viaggiatreno.py new file mode 100644 index 00000000000..37e7e020cc9 --- /dev/null +++ b/homeassistant/components/sensor/viaggiatreno.py @@ -0,0 +1,187 @@ +""" +Support for information about the Italian train system using ViaggiaTreno API. + +For more details about this platform please refer to the documentation at +https://home-assistant.io/components/sensor.viaggiatreno +""" +import logging + +import asyncio +import async_timeout +import aiohttp + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = "Powered by ViaggiaTreno Data" +VIAGGIATRENO_ENDPOINT = ("http://www.viaggiatreno.it/viaggiatrenonew/" + "resteasy/viaggiatreno/andamentoTreno/" + "{station_id}/{train_id}") + +REQUEST_TIMEOUT = 5 # seconds +ICON = 'mdi:train' +MONITORED_INFO = [ + 'categoria', + 'compOrarioArrivoZeroEffettivo', + 'compOrarioPartenzaZeroEffettivo', + 'destinazione', + 'numeroTreno', + 'orarioArrivo', + 'orarioPartenza', + 'origine', + 'subTitle', + ] + +DEFAULT_NAME = "Train {}" + +CONF_NAME = 'train_name' +CONF_STATION_ID = 'station_id' +CONF_STATION_NAME = 'station_name' +CONF_TRAIN_ID = 'train_id' + +ARRIVED_STRING = 'Arrived' +CANCELLED_STRING = 'Cancelled' +NOT_DEPARTED_STRING = "Not departed yet" +NO_INFORMATION_STRING = "No information for this train now" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_TRAIN_ID): cv.string, + vol.Required(CONF_STATION_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, + }) + + +@asyncio.coroutine +def async_setup_platform(hass, config, + async_add_devices, discovery_info=None): + """Setup the ViaggiaTreno platform.""" + train_id = config.get(CONF_TRAIN_ID) + station_id = config.get(CONF_STATION_ID) + name = config.get(CONF_NAME) + if not name: + name = DEFAULT_NAME.format(train_id) + async_add_devices([ViaggiaTrenoSensor(train_id, station_id, name)]) + + +@asyncio.coroutine +def async_http_request(hass, uri): + """Perform actual request.""" + try: + session = hass.helpers.aiohttp_client.async_get_clientsession(hass) + with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): + req = yield from session.get(uri) + if req.status != 200: + return {'error': req.status} + else: + json_response = yield from req.json() + return json_response + except (asyncio.TimeoutError, aiohttp.ClientError) as exc: + _LOGGER.error("Cannot connect to ViaggiaTreno API endpoint: %s", exc) + except ValueError: + _LOGGER.error("Received non-JSON data from ViaggiaTreno API endpoint") + + +class ViaggiaTrenoSensor(Entity): + """Implementation of a ViaggiaTreno sensor.""" + + def __init__(self, train_id, station_id, name): + """Initialize the sensor.""" + self._state = None + self._attributes = {} + self._unit = '' + self._icon = ICON + self._station_id = station_id + self._name = name + + self.uri = VIAGGIATRENO_ENDPOINT.format( + station_id=station_id, + train_id=train_id) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._unit + + @property + def device_state_attributes(self): + """Return extra attributes.""" + self._attributes[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + return self._attributes + + @staticmethod + def has_departed(data): + """Check if the train has actually departed.""" + try: + first_station = data['fermate'][0] + if data['oraUltimoRilevamento'] or first_station['effettiva']: + return True + except ValueError: + _LOGGER.error("Cannot fetch first station: %s", data) + return False + + @staticmethod + def has_arrived(data): + """Check if the train has already arrived.""" + last_station = data['fermate'][-1] + if not last_station['effettiva']: + return False + return True + + @staticmethod + def is_cancelled(data): + """Check if the train is cancelled.""" + if data['tipoTreno'] == 'ST' and data['provvedimento'] == 1: + return True + return False + + @asyncio.coroutine + def async_update(self): + """Update state.""" + uri = self.uri + res = yield from async_http_request(self.hass, uri) + if res.get('error', ''): + if res['error'] == 204: + self._state = NO_INFORMATION_STRING + self._unit = '' + else: + self._state = "Error: {}".format(res['error']) + self._unit = '' + else: + for i in MONITORED_INFO: + self._attributes[i] = res[i] + + if self.is_cancelled(res): + self._state = CANCELLED_STRING + self._icon = 'mdi:cancel' + self._unit = '' + elif not self.has_departed(res): + self._state = NOT_DEPARTED_STRING + self._unit = '' + elif self.has_arrived(res): + self._state = ARRIVED_STRING + self._unit = '' + else: + self._state = res.get('ritardo') + self._unit = 'min' + self._icon = ICON