mirror of
https://github.com/home-assistant/core.git
synced 2025-06-25 01:21:51 +02:00
Added buienradar sensor and weather (#7592)
* Added buienradar sensor and weather * used external library for parsing * used external library for parsing * updated buienradar lib to 0.4 * Make sure you import 3rd party libraries inside methods. * Make sure you import 3rd party libraries inside methods. * clean up code; optimized * imports, sensor name and attributes * updated requirements to match imports * use asyncio for http get
This commit is contained in:
committed by
Paulus Schoutsen
parent
c29553517f
commit
549133a062
@ -375,6 +375,7 @@ omit =
|
||||
homeassistant/components/sensor/blockchain.py
|
||||
homeassistant/components/sensor/bom.py
|
||||
homeassistant/components/sensor/broadlink.py
|
||||
homeassistant/components/sensor/buienradar.py
|
||||
homeassistant/components/sensor/dublin_bus_transport.py
|
||||
homeassistant/components/sensor/coinmarketcap.py
|
||||
homeassistant/components/sensor/cert_expiry.py
|
||||
@ -497,6 +498,7 @@ omit =
|
||||
homeassistant/components/tts/picotts.py
|
||||
homeassistant/components/upnp.py
|
||||
homeassistant/components/weather/bom.py
|
||||
homeassistant/components/weather/buienradar.py
|
||||
homeassistant/components/weather/metoffice.py
|
||||
homeassistant/components/weather/openweathermap.py
|
||||
homeassistant/components/weather/zamg.py
|
||||
|
327
homeassistant/components/sensor/buienradar.py
Executable file
327
homeassistant/components/sensor/buienradar.py
Executable file
@ -0,0 +1,327 @@
|
||||
"""
|
||||
Support for Buienradar.nl weather service.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.buienradar/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
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, CONF_LATITUDE, CONF_LONGITUDE,
|
||||
CONF_MONITORED_CONDITIONS, CONF_NAME, TEMP_CELSIUS)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_point_in_utc_time)
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['buienradar==0.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Supported sensor types:
|
||||
SENSOR_TYPES = {
|
||||
'stationname': ['Stationname', None, None],
|
||||
'symbol': ['Symbol', None, None],
|
||||
'humidity': ['Humidity', '%', 'mdi:water-percent'],
|
||||
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
|
||||
'groundtemperature': ['Ground Temperature', TEMP_CELSIUS,
|
||||
'mdi:thermometer'],
|
||||
'windspeed': ['Wind speed', 'm/s', 'mdi:weather-windy'],
|
||||
'windforce': ['Wind force', 'Bft', 'mdi:weather-windy'],
|
||||
'winddirection': ['Wind direction', '°', 'mdi:compass-outline'],
|
||||
'windazimuth': ['Wind direction azimuth', None, 'mdi:compass-outline'],
|
||||
'pressure': ['Pressure', 'hPa', 'mdi:gauge'],
|
||||
'visibility': ['Visibility', 'm', None],
|
||||
'windgust': ['Wind gust', 'm/s', 'mdi:weather-windy'],
|
||||
'precipitation': ['Precipitation', 'mm/h', 'mdi:weather-pouring'],
|
||||
'irradiance': ['Irradiance', 'W/m2', 'mdi:sunglasses'],
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS,
|
||||
default=['symbol', 'temperature']): vol.All(
|
||||
cv.ensure_list, vol.Length(min=1),
|
||||
[vol.In(SENSOR_TYPES.keys())]),
|
||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup the buienradar sensor."""
|
||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
|
||||
if None in (latitude, longitude):
|
||||
_LOGGER.error("Latitude or longitude not set in HomeAssistant config")
|
||||
return False
|
||||
|
||||
coordinates = {CONF_LATITUDE: float(latitude),
|
||||
CONF_LONGITUDE: float(longitude)}
|
||||
|
||||
dev = []
|
||||
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
|
||||
dev.append(BrSensor(sensor_type, config.get(CONF_NAME, 'br')))
|
||||
async_add_devices(dev)
|
||||
|
||||
data = BrData(hass, coordinates, dev)
|
||||
# schedule the first update in 1 minute from now:
|
||||
_LOGGER.debug("Start running....")
|
||||
yield from data.schedule_update(1)
|
||||
|
||||
|
||||
class BrSensor(Entity):
|
||||
"""Representation of an Buienradar sensor."""
|
||||
|
||||
def __init__(self, sensor_type, client_name):
|
||||
"""Initialize the sensor."""
|
||||
self.client_name = client_name
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self.type = sensor_type
|
||||
self._state = None
|
||||
self._unit_of_measurement = SENSOR_TYPES[self.type][1]
|
||||
self._entity_picture = None
|
||||
self._attribution = None
|
||||
self._stationname = None
|
||||
|
||||
def load_data(self, data):
|
||||
"""Load the sensor with relevant data."""
|
||||
# Find sensor
|
||||
from buienradar.buienradar import (ATTRIBUTION, IMAGE,
|
||||
STATIONNAME, SYMBOL)
|
||||
|
||||
self._attribution = data.get(ATTRIBUTION)
|
||||
self._stationname = data.get(STATIONNAME)
|
||||
if self.type == SYMBOL:
|
||||
# update weather symbol & status text
|
||||
new_state = data.get(self.type)
|
||||
img = data.get(IMAGE)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
if new_state != self._state or img != self._entity_picture:
|
||||
self._state = new_state
|
||||
self._entity_picture = img
|
||||
return True
|
||||
else:
|
||||
# update all other sensors
|
||||
new_state = data.get(self.type)
|
||||
# pylint: disable=protected-access
|
||||
if new_state != self._state:
|
||||
self._state = new_state
|
||||
return True
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return self._attribution
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return '{} {}'.format(self.client_name, self._name)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def should_poll(self): # pylint: disable=no-self-use
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def entity_picture(self):
|
||||
"""Weather symbol if type is symbol."""
|
||||
from buienradar.buienradar import SYMBOL
|
||||
|
||||
if self.type != SYMBOL:
|
||||
return None
|
||||
else:
|
||||
return self._entity_picture
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
ATTR_ATTRIBUTION: self._attribution,
|
||||
SENSOR_TYPES['stationname'][0]: self._stationname,
|
||||
}
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return possible sensor specific icon."""
|
||||
return SENSOR_TYPES[self.type][2]
|
||||
|
||||
|
||||
class BrData(object):
|
||||
"""Get the latest data and updates the states."""
|
||||
|
||||
def __init__(self, hass, coordinates, devices):
|
||||
"""Initialize the data object."""
|
||||
self.devices = devices
|
||||
self.data = {}
|
||||
self.hass = hass
|
||||
self.coordinates = coordinates
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_devices(self):
|
||||
"""Update all devices/sensors."""
|
||||
if self.devices:
|
||||
tasks = []
|
||||
# Update all devices
|
||||
for dev in self.devices:
|
||||
if dev.load_data(self.data):
|
||||
tasks.append(dev.async_update_ha_state())
|
||||
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=self.hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def schedule_update(self, minute=1):
|
||||
"""Schedule an update after minute minutes."""
|
||||
_LOGGER.debug("Scheduling next update in %s minutes.", minute)
|
||||
nxt = dt_util.utcnow() + timedelta(minutes=minute)
|
||||
async_track_point_in_utc_time(self.hass, self.async_update,
|
||||
nxt)
|
||||
|
||||
@asyncio.coroutine
|
||||
def get_data(self, url):
|
||||
"""Load xmpl data from specified url."""
|
||||
from buienradar.buienradar import (CONTENT,
|
||||
MESSAGE, STATUS_CODE, SUCCESS)
|
||||
|
||||
_LOGGER.debug("Calling url: %s...", url)
|
||||
result = {SUCCESS: False, MESSAGE: None}
|
||||
resp = None
|
||||
try:
|
||||
websession = async_get_clientsession(self.hass)
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
resp = yield from websession.get(url)
|
||||
|
||||
result[SUCCESS] = (resp.status == 200)
|
||||
result[STATUS_CODE] = resp.status
|
||||
result[CONTENT] = yield from resp.text()
|
||||
|
||||
return result
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||
result[MESSAGE] = "%s" % err
|
||||
return result
|
||||
finally:
|
||||
if resp is not None:
|
||||
yield from resp.release()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self, *_):
|
||||
"""Update the data from buienradar."""
|
||||
from buienradar.buienradar import (parse_data, CONTENT,
|
||||
DATA, MESSAGE, STATUS_CODE, SUCCESS)
|
||||
|
||||
result = yield from self.get_data('http://xml.buienradar.nl')
|
||||
if result.get(SUCCESS, False) is False:
|
||||
result = yield from self.get_data('http://api.buienradar.nl')
|
||||
|
||||
if result.get(SUCCESS):
|
||||
result = parse_data(result.get(CONTENT),
|
||||
latitude=self.coordinates[CONF_LATITUDE],
|
||||
longitude=self.coordinates[CONF_LONGITUDE])
|
||||
if result.get(SUCCESS):
|
||||
self.data = result.get(DATA)
|
||||
|
||||
yield from self.update_devices()
|
||||
|
||||
yield from self.schedule_update(10)
|
||||
else:
|
||||
yield from self.schedule_update(2)
|
||||
else:
|
||||
# unable to get the data
|
||||
_LOGGER.warning("Unable to retrieve data from Buienradar."
|
||||
"(Msg: %s, status: %s,)",
|
||||
result.get(MESSAGE),
|
||||
result.get(STATUS_CODE),)
|
||||
# schedule new call
|
||||
yield from self.schedule_update(2)
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
from buienradar.buienradar import ATTRIBUTION
|
||||
return self.data.get(ATTRIBUTION)
|
||||
|
||||
@property
|
||||
def stationname(self):
|
||||
"""Return the name of the selected weatherstation."""
|
||||
from buienradar.buienradar import STATIONNAME
|
||||
return self.data.get(STATIONNAME)
|
||||
|
||||
@property
|
||||
def condition(self):
|
||||
"""Return the condition."""
|
||||
from buienradar.buienradar import SYMBOL
|
||||
return self.data.get(SYMBOL)
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
"""Return the temperature, or None."""
|
||||
from buienradar.buienradar import TEMPERATURE
|
||||
try:
|
||||
return float(self.data.get(TEMPERATURE))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
"""Return the pressure, or None."""
|
||||
from buienradar.buienradar import PRESSURE
|
||||
try:
|
||||
return float(self.data.get(PRESSURE))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
"""Return the humidity, or None."""
|
||||
from buienradar.buienradar import HUMIDITY
|
||||
try:
|
||||
return int(self.data.get(HUMIDITY))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def wind_speed(self):
|
||||
"""Return the windspeed, or None."""
|
||||
from buienradar.buienradar import WINDSPEED
|
||||
try:
|
||||
return float(self.data.get(WINDSPEED))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def wind_bearing(self):
|
||||
"""Return the wind bearing, or None."""
|
||||
from buienradar.buienradar import WINDDIRECTION
|
||||
try:
|
||||
return int(self.data.get(WINDDIRECTION))
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast data."""
|
||||
from buienradar.buienradar import FORECAST
|
||||
return self.data.get(FORECAST)
|
119
homeassistant/components/weather/buienradar.py
Executable file
119
homeassistant/components/weather/buienradar.py
Executable file
@ -0,0 +1,119 @@
|
||||
"""
|
||||
Support for Buienradar.nl weather service.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/weather.buienradar/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from homeassistant.components.weather import (
|
||||
WeatherEntity, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import \
|
||||
CONF_NAME, TEMP_CELSIUS, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
# Reuse data and API logic from the sensor implementation
|
||||
from homeassistant.components.sensor.buienradar import (
|
||||
BrData)
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_time_interval)
|
||||
import voluptuous as vol
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FORECAST = 'forecast'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
vol.Optional(CONF_FORECAST, default=True): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the buienradar platform."""
|
||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
|
||||
if None in (latitude, longitude):
|
||||
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
|
||||
return False
|
||||
|
||||
coordinates = {CONF_LATITUDE: float(latitude),
|
||||
CONF_LONGITUDE: float(longitude)}
|
||||
|
||||
# create weather data:
|
||||
data = BrData(hass, coordinates, None)
|
||||
# create weather device:
|
||||
async_add_devices([BrWeather(data, config.get(CONF_FORECAST, True),
|
||||
config.get(CONF_NAME, None))])
|
||||
|
||||
# Update weather every 10 minutes, since
|
||||
# the data gets updated every 10 minutes
|
||||
async_track_time_interval(hass, data.async_update, timedelta(minutes=10))
|
||||
# schedule the first update in 1 minute from now:
|
||||
data.schedule_update(1)
|
||||
|
||||
|
||||
class BrWeather(WeatherEntity):
|
||||
"""Representation of a weather condition."""
|
||||
|
||||
def __init__(self, data, forecast, stationname=None):
|
||||
"""Initialise the platform with a data instance and station name."""
|
||||
self._stationname = stationname
|
||||
self._forecast = forecast
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return self._data.attribution
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._stationname or 'BR {}'.format(self._data.stationname
|
||||
or '(unknown station)')
|
||||
|
||||
@property
|
||||
def condition(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.condition
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.temperature
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.pressure
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.humidity
|
||||
|
||||
@property
|
||||
def wind_speed(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.wind_speed
|
||||
|
||||
@property
|
||||
def wind_bearing(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._data.wind_bearing
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
"""Return the unit of measurement."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast."""
|
||||
if self._forecast:
|
||||
return self._data.forecast
|
@ -114,6 +114,9 @@ boto3==1.4.3
|
||||
# homeassistant.components.switch.broadlink
|
||||
broadlink==0.3
|
||||
|
||||
# homeassistant.components.sensor.buienradar
|
||||
buienradar==0.4
|
||||
|
||||
# homeassistant.components.notify.ciscospark
|
||||
ciscosparkapi==0.4.2
|
||||
|
||||
|
Reference in New Issue
Block a user