Switch to xmltodict and pass over missing temperature (fixes #2433) (#2463)

* Switch to xmltodict and pass over missing temperature (fixes #2433)

* Add guard clauses
This commit is contained in:
Fabian Affolter 2016-07-14 03:30:11 +02:00 committed by Paulus Schoutsen
parent 675283c23e
commit a0c1c918b8
3 changed files with 65 additions and 50 deletions

View file

@ -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/ https://home-assistant.io/components/sensor.swiss_hydrological_data/
""" """
import logging import logging
import collections
from datetime import timedelta from datetime import timedelta
import voluptuous as vol import voluptuous as vol
import requests 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 import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['beautifulsoup4==4.4.1'] REQUIREMENTS = ['xmltodict==0.10.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_RESOURCE = 'http://www.hydrodata.ch/xml/SMS.xml' _RESOURCE = 'http://www.hydrodata.ch/xml/SMS.xml'
@ -39,33 +39,25 @@ ATTR_TEMPERATURE_MAX = 'Temperature max'
PLATFORM_SCHEMA = vol.Schema({ PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'swiss_hydrological_data', vol.Required(CONF_PLATFORM): 'swiss_hydrological_data',
vol.Optional(CONF_NAME): cv.string, 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. # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=30)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Swiss hydrological sensor.""" """Setup the Swiss hydrological sensor."""
from bs4 import BeautifulSoup import xmltodict
station = config.get(CONF_STATION) station = config.get(CONF_STATION)
name = config.get(CONF_NAME, DEFAULT_NAME) name = config.get(CONF_NAME, DEFAULT_NAME)
try: try:
response = requests.get(_RESOURCE, timeout=5) response = requests.get(_RESOURCE, timeout=5)
if BeautifulSoup( if any(str(station) == location.get('@StrNr') for location in
response.content, xmltodict.parse(response.text)['AKT_Data']['MesPar']) is False:
'html.parser').find(strnr='{}'.format(station)) is None: _LOGGER.error('The given station does not exist: %s', station)
_LOGGER.error('The given station does not seem to exist: %s',
station)
return False return False
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error('The URL is not accessible') _LOGGER.error('The URL is not accessible')
@ -94,29 +86,47 @@ class SwissHydrologicalDataSensor(Entity):
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any.""" """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 @property
def state(self): def state(self):
"""Return the state of the sensor.""" """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 @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
attributes = {}
if self.data.measurings is not None: if self.data.measurings is not None:
return { if '02' in self.data.measurings:
ATTR_LOCATION: self.data.measurings.location, attributes[ATTR_WATERLEVEL] = self.data.measurings['02'][
ATTR_UPDATE: self.data.measurings.update_time, 'current']
ATTR_DISCHARGE: self.data.measurings.discharge, attributes[ATTR_WATERLEVEL_MEAN] = self.data.measurings['02'][
ATTR_WATERLEVEL: self.data.measurings.waterlevel, 'mean']
ATTR_DISCHARGE_MEAN: self.data.measurings.discharge_mean, attributes[ATTR_WATERLEVEL_MAX] = self.data.measurings['02'][
ATTR_WATERLEVEL_MEAN: self.data.measurings.waterlevel_mean, 'max']
ATTR_TEMPERATURE_MEAN: self.data.measurings.temperature_mean, if '03' in self.data.measurings:
ATTR_DISCHARGE_MAX: self.data.measurings.discharge_max, attributes[ATTR_TEMPERATURE_MEAN] = self.data.measurings['03'][
ATTR_WATERLEVEL_MAX: self.data.measurings.waterlevel_max, 'mean']
ATTR_TEMPERATURE_MAX: self.data.measurings.temperature_max, 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 @property
def icon(self): def icon(self):
@ -128,7 +138,10 @@ class SwissHydrologicalDataSensor(Entity):
"""Get the latest data and update the states.""" """Get the latest data and update the states."""
self.data.update() self.data.update()
if self.data.measurings is not None: 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 # pylint: disable=too-few-public-methods
@ -143,28 +156,33 @@ class HydrologicalData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data from hydrodata.ch.""" """Get the latest data from hydrodata.ch."""
from bs4 import BeautifulSoup import xmltodict
details = {}
try: try:
response = requests.get(_RESOURCE, timeout=5) response = requests.get(_RESOURCE, timeout=5)
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error('Unable to retrieve data from %s', _RESOURCE) _LOGGER.error('Unable to retrieve data from %s', _RESOURCE)
try: 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" # Water level: Typ="02", temperature: Typ="03", discharge: Typ="10"
type02, type03, type10 = [ for station in stations:
soup.find(strnr='{}'.format(self.station), typ='{}'.format(i)) if str(self.station) != station.get('@StrNr'):
for i in ['02', '03', '10']] 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 = [] details['location'] = station.get('Name')
for entry in [type02, type03, type10]: details['update_time'] = station.get('Zeit')
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)
self.measurings = HydroData._make(details) self.measurings = details
except AttributeError: except AttributeError:
self.measurings = None self.measurings = None

View file

@ -18,8 +18,7 @@ from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['xmltodict==0.10.2']
REQUIREMENTS = ['xmltodict']
# Sensor types are defined like so: # Sensor types are defined like so:
SENSOR_TYPES = { SENSOR_TYPES = {

View file

@ -30,9 +30,6 @@ apcaccess==0.0.4
# homeassistant.components.sun # homeassistant.components.sun
astral==1.2 astral==1.2
# homeassistant.components.sensor.swiss_hydrological_data
beautifulsoup4==4.4.1
# homeassistant.components.light.blinksticklight # homeassistant.components.light.blinksticklight
blinkstick==1.1.7 blinkstick==1.1.7
@ -440,8 +437,9 @@ websocket-client==0.37.0
# homeassistant.components.zigbee # homeassistant.components.zigbee
xbee-helper==0.0.7 xbee-helper==0.0.7
# homeassistant.components.sensor.swiss_hydrological_data
# homeassistant.components.sensor.yr # homeassistant.components.sensor.yr
xmltodict xmltodict==0.10.2
# homeassistant.components.sensor.yweather # homeassistant.components.sensor.yweather
yahooweather==0.4 yahooweather==0.4