-
-
Notifications
You must be signed in to change notification settings - Fork 28.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added buienradar sensor and weather #7592
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d3c727b
Added buienradar sensor and weather
mjj4791 8a7d5f3
used external library for parsing
mjj4791 541d914
used external library for parsing
mjj4791 b8b1850
updated buienradar lib to 0.4
mjj4791 fdc93eb
Make sure you import 3rd party libraries inside methods.
mjj4791 ae26912
Make sure you import 3rd party libraries inside methods.
mjj4791 23caa09
clean up code; optimized
mjj4791 decb5db
imports, sensor name and attributes
mjj4791 f1659f4
updated requirements to match imports
mjj4791 6ba48a9
use asyncio for http get
mjj4791 File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be useful to add the station name to the attributes and not returning it as its own sensor. If you run with multiple stations then you could see where the value is coming from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added station name to the returned attribution