diff --git a/homeassistant/components/sensor/swiss_hydrological_data.py b/homeassistant/components/sensor/swiss_hydrological_data.py index 6323ae555e6..5c224f30d37 100644 --- a/homeassistant/components/sensor/swiss_hydrological_data.py +++ b/homeassistant/components/sensor/swiss_hydrological_data.py @@ -5,18 +5,18 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.swiss_hydrological_data/ """ import logging -import collections from datetime import timedelta import voluptuous as vol import requests -from homeassistant.const import (TEMP_CELSIUS, CONF_PLATFORM, CONF_NAME) +from homeassistant.const import (TEMP_CELSIUS, CONF_PLATFORM, CONF_NAME, + STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ['beautifulsoup4==4.4.1'] +REQUIREMENTS = ['xmltodict==0.10.2'] _LOGGER = logging.getLogger(__name__) _RESOURCE = 'http://www.hydrodata.ch/xml/SMS.xml' @@ -39,33 +39,25 @@ ATTR_TEMPERATURE_MAX = 'Temperature max' PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_PLATFORM): 'swiss_hydrological_data', vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_STATION): cv.string, + vol.Required(CONF_STATION): vol.Coerce(int), }) -HydroData = collections.namedtuple( - "HydrologicalData", - ['waterlevel', 'waterlevel_max', 'waterlevel_mean', 'temperature', - 'temperature_max', 'temperature_mean', 'discharge', 'discharge_max', - 'discharge_mean', 'location', 'update_time']) - # Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Swiss hydrological sensor.""" - from bs4 import BeautifulSoup + import xmltodict station = config.get(CONF_STATION) name = config.get(CONF_NAME, DEFAULT_NAME) try: response = requests.get(_RESOURCE, timeout=5) - if BeautifulSoup( - response.content, - 'html.parser').find(strnr='{}'.format(station)) is None: - _LOGGER.error('The given station does not seem to exist: %s', - station) + if any(str(station) == location.get('@StrNr') for location in + xmltodict.parse(response.text)['AKT_Data']['MesPar']) is False: + _LOGGER.error('The given station does not exist: %s', station) return False except requests.exceptions.ConnectionError: _LOGGER.error('The URL is not accessible') @@ -94,29 +86,47 @@ class SwissHydrologicalDataSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement + if self._state is not STATE_UNKNOWN: + return self._unit_of_measurement + else: + return None @property def state(self): """Return the state of the sensor.""" - return round(float(self._state), 1) + try: + return round(float(self._state), 1) + except ValueError: + return STATE_UNKNOWN @property def device_state_attributes(self): """Return the state attributes.""" + attributes = {} if self.data.measurings is not None: - return { - ATTR_LOCATION: self.data.measurings.location, - ATTR_UPDATE: self.data.measurings.update_time, - ATTR_DISCHARGE: self.data.measurings.discharge, - ATTR_WATERLEVEL: self.data.measurings.waterlevel, - ATTR_DISCHARGE_MEAN: self.data.measurings.discharge_mean, - ATTR_WATERLEVEL_MEAN: self.data.measurings.waterlevel_mean, - ATTR_TEMPERATURE_MEAN: self.data.measurings.temperature_mean, - ATTR_DISCHARGE_MAX: self.data.measurings.discharge_max, - ATTR_WATERLEVEL_MAX: self.data.measurings.waterlevel_max, - ATTR_TEMPERATURE_MAX: self.data.measurings.temperature_max, - } + if '02' in self.data.measurings: + attributes[ATTR_WATERLEVEL] = self.data.measurings['02'][ + 'current'] + attributes[ATTR_WATERLEVEL_MEAN] = self.data.measurings['02'][ + 'mean'] + attributes[ATTR_WATERLEVEL_MAX] = self.data.measurings['02'][ + 'max'] + if '03' in self.data.measurings: + attributes[ATTR_TEMPERATURE_MEAN] = self.data.measurings['03'][ + 'mean'] + attributes[ATTR_TEMPERATURE_MAX] = self.data.measurings['03'][ + 'max'] + if '10' in self.data.measurings: + attributes[ATTR_DISCHARGE] = self.data.measurings['10'][ + 'current'] + attributes[ATTR_DISCHARGE_MEAN] = self.data.measurings['10'][ + 'current'] + attributes[ATTR_DISCHARGE_MAX] = self.data.measurings['10'][ + 'max'] + + attributes[ATTR_LOCATION] = self.data.measurings['location'] + attributes[ATTR_UPDATE] = self.data.measurings['update_time'] + return attributes @property def icon(self): @@ -128,7 +138,10 @@ class SwissHydrologicalDataSensor(Entity): """Get the latest data and update the states.""" self.data.update() if self.data.measurings is not None: - self._state = self.data.measurings.temperature + if '03' not in self.data.measurings: + self._state = STATE_UNKNOWN + else: + self._state = self.data.measurings['03']['current'] # pylint: disable=too-few-public-methods @@ -143,28 +156,33 @@ class HydrologicalData(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data from hydrodata.ch.""" - from bs4 import BeautifulSoup + import xmltodict + details = {} try: response = requests.get(_RESOURCE, timeout=5) except requests.exceptions.ConnectionError: _LOGGER.error('Unable to retrieve data from %s', _RESOURCE) try: - soup = BeautifulSoup(response.content, 'html.parser') + stations = xmltodict.parse(response.text)['AKT_Data']['MesPar'] # Water level: Typ="02", temperature: Typ="03", discharge: Typ="10" - type02, type03, type10 = [ - soup.find(strnr='{}'.format(self.station), typ='{}'.format(i)) - for i in ['02', '03', '10']] + for station in stations: + if str(self.station) != station.get('@StrNr'): + continue + for data in ['02', '03', '10']: + if data != station.get('@Typ'): + continue + values = station.get('Wert') + if values is not None: + details[data] = { + 'current': values[0], + 'max': list(values[4].items())[1][1], + 'mean': list(values[3].items())[1][1]} - details = [] - for entry in [type02, type03, type10]: - details.append(entry.wert.string) - details.append(entry.find(typ="max24").string) - details.append(entry.find(typ="m24").string) - details.append(type03.find('name').string) - details.append(type03.find('zeit').string) + details['location'] = station.get('Name') + details['update_time'] = station.get('Zeit') - self.measurings = HydroData._make(details) + self.measurings = details except AttributeError: self.measurings = None diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index ddfbc68d974..3407838899e 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -18,8 +18,7 @@ from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) - -REQUIREMENTS = ['xmltodict'] +REQUIREMENTS = ['xmltodict==0.10.2'] # Sensor types are defined like so: SENSOR_TYPES = { diff --git a/requirements_all.txt b/requirements_all.txt index e1ad3d83efb..3b9339cdfe2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -30,9 +30,6 @@ apcaccess==0.0.4 # homeassistant.components.sun astral==1.2 -# homeassistant.components.sensor.swiss_hydrological_data -beautifulsoup4==4.4.1 - # homeassistant.components.light.blinksticklight blinkstick==1.1.7 @@ -440,8 +437,9 @@ websocket-client==0.37.0 # homeassistant.components.zigbee xbee-helper==0.0.7 +# homeassistant.components.sensor.swiss_hydrological_data # homeassistant.components.sensor.yr -xmltodict +xmltodict==0.10.2 # homeassistant.components.sensor.yweather yahooweather==0.4