2019-04-03 15:40:03 +00:00
|
|
|
"""Support for Buienradar.nl weather service."""
|
2017-06-05 06:48:11 +00:00
|
|
|
import asyncio
|
2018-07-23 10:37:23 +00:00
|
|
|
from datetime import datetime, timedelta
|
2017-06-05 06:48:11 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
import aiohttp
|
2019-03-21 05:56:46 +00:00
|
|
|
import async_timeout
|
2017-06-05 06:48:11 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|
|
|
from homeassistant.const import (
|
2019-03-21 05:56:46 +00:00
|
|
|
ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS,
|
|
|
|
CONF_NAME, TEMP_CELSIUS)
|
2017-06-05 06:48:11 +00:00
|
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
2019-03-21 05:56:46 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2017-06-05 06:48:11 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
2017-06-05 06:48:11 +00:00
|
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2017-08-01 03:37:33 +00:00
|
|
|
MEASURED_LABEL = 'Measured'
|
2017-07-07 04:39:28 +00:00
|
|
|
TIMEFRAME_LABEL = 'Timeframe'
|
2017-08-16 06:07:04 +00:00
|
|
|
SYMBOL = 'symbol'
|
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
# Schedule next call after (minutes):
|
|
|
|
SCHEDULE_OK = 10
|
|
|
|
# When an error occurred, new call after (minutes):
|
|
|
|
SCHEDULE_NOK = 2
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
# Supported sensor types:
|
2017-07-07 04:39:28 +00:00
|
|
|
# Key: ['label', unit, icon]
|
2017-06-05 06:48:11 +00:00
|
|
|
SENSOR_TYPES = {
|
|
|
|
'stationname': ['Stationname', None, None],
|
2019-07-28 20:08:20 +00:00
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'barometerfc': ['Barometer value', None, 'mdi:gauge'],
|
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'barometerfcname': ['Barometer', None, 'mdi:gauge'],
|
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'barometerfcnamenl': ['Barometer', None, 'mdi:gauge'],
|
2017-08-16 06:07:04 +00:00
|
|
|
'condition': ['Condition', None, None],
|
|
|
|
'conditioncode': ['Condition code', None, None],
|
|
|
|
'conditiondetailed': ['Detailed condition', None, None],
|
|
|
|
'conditionexact': ['Full condition', None, None],
|
2017-06-05 06:48:11 +00:00
|
|
|
'symbol': ['Symbol', None, None],
|
2019-07-28 20:08:20 +00:00
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'feeltemperature': ['Feel temperature', TEMP_CELSIUS, 'mdi:thermometer'],
|
2017-06-05 06:48:11 +00:00
|
|
|
'humidity': ['Humidity', '%', 'mdi:water-percent'],
|
|
|
|
'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'],
|
2017-08-01 03:37:33 +00:00
|
|
|
'groundtemperature': ['Ground temperature', TEMP_CELSIUS,
|
2017-06-05 06:48:11 +00:00
|
|
|
'mdi:thermometer'],
|
2019-07-28 20:08:20 +00:00
|
|
|
'windspeed': ['Wind speed', 'km/h', 'mdi:weather-windy'],
|
2017-06-05 06:48:11 +00:00
|
|
|
'windforce': ['Wind force', 'Bft', 'mdi:weather-windy'],
|
2017-06-24 05:51:45 +00:00
|
|
|
'winddirection': ['Wind direction', None, 'mdi:compass-outline'],
|
|
|
|
'windazimuth': ['Wind direction azimuth', '°', 'mdi:compass-outline'],
|
2017-06-05 06:48:11 +00:00
|
|
|
'pressure': ['Pressure', 'hPa', 'mdi:gauge'],
|
2019-07-28 20:08:20 +00:00
|
|
|
'visibility': ['Visibility', 'km', None],
|
|
|
|
'windgust': ['Wind gust', 'km/h', 'mdi:weather-windy'],
|
2017-06-05 06:48:11 +00:00
|
|
|
'precipitation': ['Precipitation', 'mm/h', 'mdi:weather-pouring'],
|
|
|
|
'irradiance': ['Irradiance', 'W/m2', 'mdi:sunglasses'],
|
2017-06-24 07:12:52 +00:00
|
|
|
'precipitation_forecast_average': ['Precipitation forecast average',
|
|
|
|
'mm/h', 'mdi:weather-pouring'],
|
|
|
|
'precipitation_forecast_total': ['Precipitation forecast total',
|
2017-08-16 06:07:04 +00:00
|
|
|
'mm', 'mdi:weather-pouring'],
|
2019-07-28 20:08:20 +00:00
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'rainlast24hour': ['Rain last 24h', 'mm', 'mdi:weather-pouring'],
|
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'rainlasthour': ['Rain last hour', 'mm', 'mdi:weather-pouring'],
|
2017-08-16 06:07:04 +00:00
|
|
|
'temperature_1d': ['Temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'temperature_2d': ['Temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'temperature_3d': ['Temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'temperature_4d': ['Temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'temperature_5d': ['Temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'mintemp_1d': ['Minimum temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'mintemp_2d': ['Minimum temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'mintemp_3d': ['Minimum temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'mintemp_4d': ['Minimum temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'mintemp_5d': ['Minimum temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'],
|
|
|
|
'rain_1d': ['Rain 1d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'rain_2d': ['Rain 2d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'rain_3d': ['Rain 3d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'rain_4d': ['Rain 4d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'rain_5d': ['Rain 5d', 'mm', 'mdi:weather-pouring'],
|
2019-07-28 20:08:20 +00:00
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'minrain_1d': ['Minimum rain 1d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'minrain_2d': ['Minimum rain 2d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'minrain_3d': ['Minimum rain 3d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'minrain_4d': ['Minimum rain 4d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'minrain_5d': ['Minimum rain 5d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
# new in json api (>1.0.0):
|
|
|
|
'maxrain_1d': ['Maximum rain 1d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'maxrain_2d': ['Maximum rain 2d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'maxrain_3d': ['Maximum rain 3d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'maxrain_4d': ['Maximum rain 4d', 'mm', 'mdi:weather-pouring'],
|
|
|
|
'maxrain_5d': ['Maximum rain 5d', 'mm', 'mdi:weather-pouring'],
|
2017-08-16 06:07:04 +00:00
|
|
|
'rainchance_1d': ['Rainchance 1d', '%', 'mdi:weather-pouring'],
|
|
|
|
'rainchance_2d': ['Rainchance 2d', '%', 'mdi:weather-pouring'],
|
|
|
|
'rainchance_3d': ['Rainchance 3d', '%', 'mdi:weather-pouring'],
|
|
|
|
'rainchance_4d': ['Rainchance 4d', '%', 'mdi:weather-pouring'],
|
|
|
|
'rainchance_5d': ['Rainchance 5d', '%', 'mdi:weather-pouring'],
|
|
|
|
'sunchance_1d': ['Sunchance 1d', '%', 'mdi:weather-partlycloudy'],
|
|
|
|
'sunchance_2d': ['Sunchance 2d', '%', 'mdi:weather-partlycloudy'],
|
|
|
|
'sunchance_3d': ['Sunchance 3d', '%', 'mdi:weather-partlycloudy'],
|
|
|
|
'sunchance_4d': ['Sunchance 4d', '%', 'mdi:weather-partlycloudy'],
|
|
|
|
'sunchance_5d': ['Sunchance 5d', '%', 'mdi:weather-partlycloudy'],
|
|
|
|
'windforce_1d': ['Wind force 1d', 'Bft', 'mdi:weather-windy'],
|
|
|
|
'windforce_2d': ['Wind force 2d', 'Bft', 'mdi:weather-windy'],
|
|
|
|
'windforce_3d': ['Wind force 3d', 'Bft', 'mdi:weather-windy'],
|
|
|
|
'windforce_4d': ['Wind force 4d', 'Bft', 'mdi:weather-windy'],
|
|
|
|
'windforce_5d': ['Wind force 5d', 'Bft', 'mdi:weather-windy'],
|
2019-07-28 20:08:20 +00:00
|
|
|
'windspeed_1d': ['Wind speed 1d', 'km/h', 'mdi:weather-windy'],
|
|
|
|
'windspeed_2d': ['Wind speed 2d', 'km/h', 'mdi:weather-windy'],
|
|
|
|
'windspeed_3d': ['Wind speed 3d', 'km/h', 'mdi:weather-windy'],
|
|
|
|
'windspeed_4d': ['Wind speed 4d', 'km/h', 'mdi:weather-windy'],
|
|
|
|
'windspeed_5d': ['Wind speed 5d', 'km/h', 'mdi:weather-windy'],
|
|
|
|
'winddirection_1d': ['Wind direction 1d', None, 'mdi:compass-outline'],
|
|
|
|
'winddirection_2d': ['Wind direction 2d', None, 'mdi:compass-outline'],
|
|
|
|
'winddirection_3d': ['Wind direction 3d', None, 'mdi:compass-outline'],
|
|
|
|
'winddirection_4d': ['Wind direction 4d', None, 'mdi:compass-outline'],
|
|
|
|
'winddirection_5d': ['Wind direction 5d', None, 'mdi:compass-outline'],
|
|
|
|
'windazimuth_1d': ['Wind direction azimuth 1d', '°',
|
|
|
|
'mdi:compass-outline'],
|
|
|
|
'windazimuth_2d': ['Wind direction azimuth 2d', '°',
|
|
|
|
'mdi:compass-outline'],
|
|
|
|
'windazimuth_3d': ['Wind direction azimuth 3d', '°',
|
|
|
|
'mdi:compass-outline'],
|
|
|
|
'windazimuth_4d': ['Wind direction azimuth 4d', '°',
|
|
|
|
'mdi:compass-outline'],
|
|
|
|
'windazimuth_5d': ['Wind direction azimuth 5d', '°',
|
|
|
|
'mdi:compass-outline'],
|
2017-08-16 06:07:04 +00:00
|
|
|
'condition_1d': ['Condition 1d', None, None],
|
|
|
|
'condition_2d': ['Condition 2d', None, None],
|
|
|
|
'condition_3d': ['Condition 3d', None, None],
|
|
|
|
'condition_4d': ['Condition 4d', None, None],
|
|
|
|
'condition_5d': ['Condition 5d', None, None],
|
|
|
|
'conditioncode_1d': ['Condition code 1d', None, None],
|
|
|
|
'conditioncode_2d': ['Condition code 2d', None, None],
|
|
|
|
'conditioncode_3d': ['Condition code 3d', None, None],
|
|
|
|
'conditioncode_4d': ['Condition code 4d', None, None],
|
|
|
|
'conditioncode_5d': ['Condition code 5d', None, None],
|
|
|
|
'conditiondetailed_1d': ['Detailed condition 1d', None, None],
|
|
|
|
'conditiondetailed_2d': ['Detailed condition 2d', None, None],
|
|
|
|
'conditiondetailed_3d': ['Detailed condition 3d', None, None],
|
|
|
|
'conditiondetailed_4d': ['Detailed condition 4d', None, None],
|
|
|
|
'conditiondetailed_5d': ['Detailed condition 5d', None, None],
|
|
|
|
'conditionexact_1d': ['Full condition 1d', None, None],
|
|
|
|
'conditionexact_2d': ['Full condition 2d', None, None],
|
|
|
|
'conditionexact_3d': ['Full condition 3d', None, None],
|
|
|
|
'conditionexact_4d': ['Full condition 4d', None, None],
|
|
|
|
'conditionexact_5d': ['Full condition 5d', None, None],
|
|
|
|
'symbol_1d': ['Symbol 1d', None, None],
|
|
|
|
'symbol_2d': ['Symbol 2d', None, None],
|
|
|
|
'symbol_3d': ['Symbol 3d', None, None],
|
|
|
|
'symbol_4d': ['Symbol 4d', None, None],
|
2019-07-28 20:08:20 +00:00
|
|
|
'symbol_5d': ['Symbol 5d', None, None]
|
2017-06-05 06:48:11 +00:00
|
|
|
}
|
|
|
|
|
2017-06-24 07:12:52 +00:00
|
|
|
CONF_TIMEFRAME = 'timeframe'
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
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())]),
|
2017-06-24 07:12:52 +00:00
|
|
|
vol.Inclusive(CONF_LATITUDE, 'coordinates',
|
|
|
|
'Latitude and longitude must exist together'): cv.latitude,
|
|
|
|
vol.Inclusive(CONF_LONGITUDE, 'coordinates',
|
|
|
|
'Latitude and longitude must exist together'): cv.longitude,
|
2017-07-07 04:39:28 +00:00
|
|
|
vol.Optional(CONF_TIMEFRAME, default=60):
|
|
|
|
vol.All(vol.Coerce(int), vol.Range(min=5, max=120)),
|
2019-02-23 13:43:37 +00:00
|
|
|
vol.Optional(CONF_NAME, default='br'): cv.string,
|
2017-06-05 06:48:11 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
|
2018-10-01 06:55:43 +00:00
|
|
|
async def async_setup_platform(hass, config, async_add_entities,
|
|
|
|
discovery_info=None):
|
2017-07-07 04:39:28 +00:00
|
|
|
"""Create the buienradar sensor."""
|
2019-03-21 14:32:13 +00:00
|
|
|
from .weather import DEFAULT_TIMEFRAME
|
2017-06-24 07:12:52 +00:00
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
|
|
|
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
2017-06-24 07:12:52 +00:00
|
|
|
timeframe = config.get(CONF_TIMEFRAME, DEFAULT_TIMEFRAME)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
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)}
|
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
_LOGGER.debug("Initializing buienradar sensor coordinate %s, timeframe %s",
|
|
|
|
coordinates, timeframe)
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
dev = []
|
|
|
|
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
|
2019-02-23 13:43:37 +00:00
|
|
|
dev.append(BrSensor(sensor_type, config.get(CONF_NAME),
|
2018-05-01 19:06:41 +00:00
|
|
|
coordinates))
|
2018-08-24 14:37:30 +00:00
|
|
|
async_add_entities(dev)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2017-06-24 07:12:52 +00:00
|
|
|
data = BrData(hass, coordinates, timeframe, dev)
|
2017-06-05 06:48:11 +00:00
|
|
|
# schedule the first update in 1 minute from now:
|
2018-10-01 06:55:43 +00:00
|
|
|
await data.schedule_update(1)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BrSensor(Entity):
|
|
|
|
"""Representation of an Buienradar sensor."""
|
|
|
|
|
2018-05-01 19:06:41 +00:00
|
|
|
def __init__(self, sensor_type, client_name, coordinates):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""Initialize the sensor."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import (PRECIPITATION_FORECAST, CONDITION)
|
2017-07-07 04:39:28 +00:00
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
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
|
2017-07-07 04:39:28 +00:00
|
|
|
self._measured = None
|
2017-06-05 06:48:11 +00:00
|
|
|
self._stationname = None
|
2018-05-01 19:06:41 +00:00
|
|
|
self._unique_id = self.uid(coordinates)
|
|
|
|
|
|
|
|
# All continuous sensors should be forced to be updated
|
|
|
|
self._force_update = self.type != SYMBOL and \
|
|
|
|
not self.type.startswith(CONDITION)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
if self.type.startswith(PRECIPITATION_FORECAST):
|
|
|
|
self._timeframe = None
|
|
|
|
|
2018-05-01 19:06:41 +00:00
|
|
|
def uid(self, coordinates):
|
|
|
|
"""Generate a unique id using coordinates and sensor type."""
|
2018-05-13 10:09:28 +00:00
|
|
|
# The combination of the location, name and sensor type is unique
|
2018-05-01 19:06:41 +00:00
|
|
|
return "%2.6f%2.6f%s" % (coordinates[CONF_LATITUDE],
|
|
|
|
coordinates[CONF_LONGITUDE],
|
|
|
|
self.type)
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
def load_data(self, data):
|
|
|
|
"""Load the sensor with relevant data."""
|
|
|
|
# Find sensor
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import (ATTRIBUTION, CONDITION, CONDCODE,
|
|
|
|
DETAILED, EXACT, EXACTNL, FORECAST,
|
|
|
|
IMAGE, MEASURED,
|
|
|
|
PRECIPITATION_FORECAST, STATIONNAME,
|
|
|
|
TIMEFRAME, VISIBILITY, WINDGUST,
|
|
|
|
WINDSPEED)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2018-05-01 19:06:41 +00:00
|
|
|
# Check if we have a new measurement,
|
|
|
|
# otherwise we do not have to update the sensor
|
|
|
|
if self._measured == data.get(MEASURED):
|
|
|
|
return False
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
self._attribution = data.get(ATTRIBUTION)
|
|
|
|
self._stationname = data.get(STATIONNAME)
|
2017-07-07 04:39:28 +00:00
|
|
|
self._measured = data.get(MEASURED)
|
2017-08-16 06:07:04 +00:00
|
|
|
|
|
|
|
if self.type.endswith('_1d') or \
|
|
|
|
self.type.endswith('_2d') or \
|
|
|
|
self.type.endswith('_3d') or \
|
|
|
|
self.type.endswith('_4d') or \
|
|
|
|
self.type.endswith('_5d'):
|
|
|
|
|
2019-07-28 20:08:20 +00:00
|
|
|
# update forcasting sensors:
|
2017-08-16 06:07:04 +00:00
|
|
|
fcday = 0
|
|
|
|
if self.type.endswith('_2d'):
|
|
|
|
fcday = 1
|
|
|
|
if self.type.endswith('_3d'):
|
|
|
|
fcday = 2
|
|
|
|
if self.type.endswith('_4d'):
|
|
|
|
fcday = 3
|
|
|
|
if self.type.endswith('_5d'):
|
|
|
|
fcday = 4
|
|
|
|
|
2019-07-28 20:08:20 +00:00
|
|
|
# update weather symbol & status text
|
2017-08-16 06:07:04 +00:00
|
|
|
if self.type.startswith(SYMBOL) or self.type.startswith(CONDITION):
|
2017-08-29 13:33:47 +00:00
|
|
|
try:
|
|
|
|
condition = data.get(FORECAST)[fcday].get(CONDITION)
|
|
|
|
except IndexError:
|
|
|
|
_LOGGER.warning("No forecast for fcday=%s...", fcday)
|
|
|
|
return False
|
|
|
|
|
2017-08-16 06:07:04 +00:00
|
|
|
if condition:
|
|
|
|
new_state = condition.get(CONDITION, None)
|
|
|
|
if self.type.startswith(SYMBOL):
|
|
|
|
new_state = condition.get(EXACTNL, None)
|
|
|
|
if self.type.startswith('conditioncode'):
|
|
|
|
new_state = condition.get(CONDCODE, None)
|
|
|
|
if self.type.startswith('conditiondetailed'):
|
|
|
|
new_state = condition.get(DETAILED, None)
|
|
|
|
if self.type.startswith('conditionexact'):
|
|
|
|
new_state = condition.get(EXACT, None)
|
|
|
|
|
|
|
|
img = condition.get(IMAGE, None)
|
|
|
|
|
|
|
|
if new_state != self._state or img != self._entity_picture:
|
|
|
|
self._state = new_state
|
|
|
|
self._entity_picture = img
|
|
|
|
return True
|
|
|
|
return False
|
2018-07-23 08:16:05 +00:00
|
|
|
|
2019-07-28 20:08:20 +00:00
|
|
|
if self.type.startswith(WINDSPEED):
|
|
|
|
# hass wants windspeeds in km/h not m/s, so convert:
|
|
|
|
try:
|
|
|
|
self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
|
|
|
|
if self._state is not None:
|
|
|
|
self._state = round(self._state * 3.6, 1)
|
|
|
|
return True
|
|
|
|
except IndexError:
|
|
|
|
_LOGGER.warning("No forecast for fcday=%s...", fcday)
|
|
|
|
return False
|
|
|
|
|
|
|
|
# update all other sensors
|
2018-07-23 08:16:05 +00:00
|
|
|
try:
|
|
|
|
self._state = data.get(FORECAST)[fcday].get(self.type[:-3])
|
|
|
|
return True
|
|
|
|
except IndexError:
|
|
|
|
_LOGGER.warning("No forecast for fcday=%s...", fcday)
|
|
|
|
return False
|
2017-08-16 06:07:04 +00:00
|
|
|
|
|
|
|
if self.type == SYMBOL or self.type.startswith(CONDITION):
|
2017-06-05 06:48:11 +00:00
|
|
|
# update weather symbol & status text
|
2017-08-16 06:07:04 +00:00
|
|
|
condition = data.get(CONDITION, None)
|
|
|
|
if condition:
|
|
|
|
if self.type == SYMBOL:
|
|
|
|
new_state = condition.get(EXACTNL, None)
|
|
|
|
if self.type == CONDITION:
|
|
|
|
new_state = condition.get(CONDITION, None)
|
|
|
|
if self.type == 'conditioncode':
|
|
|
|
new_state = condition.get(CONDCODE, None)
|
|
|
|
if self.type == 'conditiondetailed':
|
|
|
|
new_state = condition.get(DETAILED, None)
|
|
|
|
if self.type == 'conditionexact':
|
|
|
|
new_state = condition.get(EXACT, None)
|
|
|
|
|
|
|
|
img = condition.get(IMAGE, None)
|
|
|
|
|
|
|
|
if new_state != self._state or img != self._entity_picture:
|
|
|
|
self._state = new_state
|
|
|
|
self._entity_picture = img
|
|
|
|
return True
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
if self.type.startswith(PRECIPITATION_FORECAST):
|
2017-06-24 07:12:52 +00:00
|
|
|
# update nested precipitation forecast sensors
|
|
|
|
nested = data.get(PRECIPITATION_FORECAST)
|
2017-07-07 04:39:28 +00:00
|
|
|
self._timeframe = nested.get(TIMEFRAME)
|
2018-05-01 19:06:41 +00:00
|
|
|
self._state = nested.get(self.type[len(PRECIPITATION_FORECAST)+1:])
|
|
|
|
return True
|
2017-07-07 04:39:28 +00:00
|
|
|
|
2019-07-28 20:08:20 +00:00
|
|
|
if self.type == WINDSPEED or self.type == WINDGUST:
|
|
|
|
# hass wants windspeeds in km/h not m/s, so convert:
|
|
|
|
self._state = data.get(self.type)
|
|
|
|
if self._state is not None:
|
|
|
|
self._state = round(data.get(self.type) * 3.6, 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
if self.type == VISIBILITY:
|
|
|
|
# hass wants visibility in km (not m), so convert:
|
|
|
|
self._state = data.get(self.type)
|
|
|
|
if self._state is not None:
|
|
|
|
self._state = round(self._state / 1000, 1)
|
|
|
|
return True
|
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
# update all other sensors
|
2018-05-01 19:06:41 +00:00
|
|
|
self._state = data.get(self.type)
|
|
|
|
return True
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def attribution(self):
|
|
|
|
"""Return the attribution."""
|
|
|
|
return self._attribution
|
|
|
|
|
2018-05-01 19:06:41 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return the unique id."""
|
|
|
|
return self._unique_id
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
@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
|
2018-06-25 17:05:07 +00:00
|
|
|
def should_poll(self):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""No polling needed."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def entity_picture(self):
|
|
|
|
"""Weather symbol if type is symbol."""
|
2017-07-06 06:30:01 +00:00
|
|
|
return self._entity_picture
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Return the state attributes."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import (PRECIPITATION_FORECAST)
|
2017-07-07 04:39:28 +00:00
|
|
|
|
|
|
|
if self.type.startswith(PRECIPITATION_FORECAST):
|
|
|
|
result = {ATTR_ATTRIBUTION: self._attribution}
|
|
|
|
if self._timeframe is not None:
|
|
|
|
result[TIMEFRAME_LABEL] = "%d min" % (self._timeframe)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2017-08-01 03:37:33 +00:00
|
|
|
result = {
|
2017-06-05 06:48:11 +00:00
|
|
|
ATTR_ATTRIBUTION: self._attribution,
|
|
|
|
SENSOR_TYPES['stationname'][0]: self._stationname,
|
|
|
|
}
|
2017-08-01 03:37:33 +00:00
|
|
|
if self._measured is not None:
|
|
|
|
# convert datetime (Europe/Amsterdam) into local datetime
|
|
|
|
local_dt = dt_util.as_local(self._measured)
|
|
|
|
result[MEASURED_LABEL] = local_dt.strftime("%c")
|
|
|
|
|
|
|
|
return result
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
@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]
|
|
|
|
|
2018-05-01 19:06:41 +00:00
|
|
|
@property
|
|
|
|
def force_update(self):
|
|
|
|
"""Return true for continuous sensors, false for discrete sensors."""
|
|
|
|
return self._force_update
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class BrData:
|
2017-06-05 06:48:11 +00:00
|
|
|
"""Get the latest data and updates the states."""
|
|
|
|
|
2017-06-24 07:12:52 +00:00
|
|
|
def __init__(self, hass, coordinates, timeframe, devices):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""Initialize the data object."""
|
|
|
|
self.devices = devices
|
|
|
|
self.data = {}
|
|
|
|
self.hass = hass
|
|
|
|
self.coordinates = coordinates
|
2017-06-24 07:12:52 +00:00
|
|
|
self.timeframe = timeframe
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2018-10-01 06:55:43 +00:00
|
|
|
async def update_devices(self):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""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:
|
2019-05-23 04:09:59 +00:00
|
|
|
await asyncio.wait(tasks)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2018-10-01 06:55:43 +00:00
|
|
|
async def schedule_update(self, minute=1):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""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)
|
|
|
|
|
2018-10-01 06:55:43 +00:00
|
|
|
async def get_data(self, url):
|
2017-07-07 04:39:28 +00:00
|
|
|
"""Load data from specified url."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import (CONTENT,
|
|
|
|
MESSAGE, STATUS_CODE, SUCCESS)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
_LOGGER.debug("Calling url: %s...", url)
|
|
|
|
result = {SUCCESS: False, MESSAGE: None}
|
|
|
|
resp = None
|
|
|
|
try:
|
|
|
|
websession = async_get_clientsession(self.hass)
|
2019-05-23 04:09:59 +00:00
|
|
|
with async_timeout.timeout(10):
|
2018-10-01 06:55:43 +00:00
|
|
|
resp = await websession.get(url)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
result[STATUS_CODE] = resp.status
|
2018-10-01 06:55:43 +00:00
|
|
|
result[CONTENT] = await resp.text()
|
2017-07-07 04:39:28 +00:00
|
|
|
if resp.status == 200:
|
|
|
|
result[SUCCESS] = True
|
|
|
|
else:
|
|
|
|
result[MESSAGE] = "Got http statuscode: %d" % (resp.status)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
return result
|
|
|
|
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
|
|
|
result[MESSAGE] = "%s" % err
|
|
|
|
return result
|
2017-07-07 04:39:28 +00:00
|
|
|
finally:
|
|
|
|
if resp is not None:
|
2018-10-01 06:55:43 +00:00
|
|
|
await resp.release()
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2018-10-01 06:55:43 +00:00
|
|
|
async def async_update(self, *_):
|
2017-06-05 06:48:11 +00:00
|
|
|
"""Update the data from buienradar."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import (CONTENT, DATA, MESSAGE,
|
|
|
|
STATUS_CODE, SUCCESS)
|
|
|
|
from buienradar.buienradar import (parse_data)
|
|
|
|
from buienradar.urls import (JSON_FEED_URL,
|
|
|
|
json_precipitation_forecast_url)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
2019-07-28 20:08:20 +00:00
|
|
|
content = await self.get_data(JSON_FEED_URL)
|
2017-06-24 07:12:52 +00:00
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
if content.get(SUCCESS) is not True:
|
|
|
|
# unable to get the data
|
2019-07-28 20:08:20 +00:00
|
|
|
_LOGGER.warning("Unable to retrieve json data from Buienradar."
|
2017-07-07 04:39:28 +00:00
|
|
|
"(Msg: %s, status: %s,)",
|
|
|
|
content.get(MESSAGE),
|
|
|
|
content.get(STATUS_CODE),)
|
|
|
|
# schedule new call
|
2018-10-01 06:55:43 +00:00
|
|
|
await self.schedule_update(SCHEDULE_NOK)
|
2017-07-07 04:39:28 +00:00
|
|
|
return
|
|
|
|
|
2017-06-24 07:12:52 +00:00
|
|
|
# rounding coordinates prevents unnecessary redirects/calls
|
2019-07-28 20:08:20 +00:00
|
|
|
lat = self.coordinates[CONF_LATITUDE]
|
|
|
|
lon = self.coordinates[CONF_LONGITUDE]
|
|
|
|
rainurl = json_precipitation_forecast_url(lat, lon)
|
2018-10-01 06:55:43 +00:00
|
|
|
raincontent = await self.get_data(rainurl)
|
2017-06-24 07:12:52 +00:00
|
|
|
|
2017-07-07 04:39:28 +00:00
|
|
|
if raincontent.get(SUCCESS) is not True:
|
2017-06-05 06:48:11 +00:00
|
|
|
# unable to get the data
|
2017-07-07 04:39:28 +00:00
|
|
|
_LOGGER.warning("Unable to retrieve raindata from Buienradar."
|
2017-06-05 06:48:11 +00:00
|
|
|
"(Msg: %s, status: %s,)",
|
2017-07-07 04:39:28 +00:00
|
|
|
raincontent.get(MESSAGE),
|
|
|
|
raincontent.get(STATUS_CODE),)
|
2017-06-05 06:48:11 +00:00
|
|
|
# schedule new call
|
2018-10-01 06:55:43 +00:00
|
|
|
await self.schedule_update(SCHEDULE_NOK)
|
2017-07-07 04:39:28 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
result = parse_data(content.get(CONTENT),
|
|
|
|
raincontent.get(CONTENT),
|
|
|
|
self.coordinates[CONF_LATITUDE],
|
|
|
|
self.coordinates[CONF_LONGITUDE],
|
2019-07-28 20:08:20 +00:00
|
|
|
self.timeframe,
|
|
|
|
False)
|
2017-07-07 04:39:28 +00:00
|
|
|
|
|
|
|
_LOGGER.debug("Buienradar parsed data: %s", result)
|
|
|
|
if result.get(SUCCESS) is not True:
|
2018-07-23 10:37:23 +00:00
|
|
|
if int(datetime.now().strftime('%H')) > 0:
|
|
|
|
_LOGGER.warning("Unable to parse data from Buienradar."
|
|
|
|
"(Msg: %s)",
|
|
|
|
result.get(MESSAGE),)
|
2018-10-01 06:55:43 +00:00
|
|
|
await self.schedule_update(SCHEDULE_NOK)
|
2017-07-07 04:39:28 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
self.data = result.get(DATA)
|
2018-10-01 06:55:43 +00:00
|
|
|
await self.update_devices()
|
|
|
|
await self.schedule_update(SCHEDULE_OK)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def attribution(self):
|
|
|
|
"""Return the attribution."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import ATTRIBUTION
|
2017-06-05 06:48:11 +00:00
|
|
|
return self.data.get(ATTRIBUTION)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def stationname(self):
|
|
|
|
"""Return the name of the selected weatherstation."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import STATIONNAME
|
2017-06-05 06:48:11 +00:00
|
|
|
return self.data.get(STATIONNAME)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def condition(self):
|
|
|
|
"""Return the condition."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import CONDITION
|
2017-08-16 06:07:04 +00:00
|
|
|
return self.data.get(CONDITION)
|
2017-06-05 06:48:11 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def temperature(self):
|
|
|
|
"""Return the temperature, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import TEMPERATURE
|
2017-06-05 06:48:11 +00:00
|
|
|
try:
|
|
|
|
return float(self.data.get(TEMPERATURE))
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def pressure(self):
|
|
|
|
"""Return the pressure, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import PRESSURE
|
2017-06-05 06:48:11 +00:00
|
|
|
try:
|
|
|
|
return float(self.data.get(PRESSURE))
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def humidity(self):
|
|
|
|
"""Return the humidity, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import HUMIDITY
|
2017-06-05 06:48:11 +00:00
|
|
|
try:
|
|
|
|
return int(self.data.get(HUMIDITY))
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
2017-08-16 06:07:04 +00:00
|
|
|
@property
|
|
|
|
def visibility(self):
|
|
|
|
"""Return the visibility, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import VISIBILITY
|
2017-08-16 06:07:04 +00:00
|
|
|
try:
|
|
|
|
return int(self.data.get(VISIBILITY))
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
2017-06-05 06:48:11 +00:00
|
|
|
@property
|
|
|
|
def wind_speed(self):
|
|
|
|
"""Return the windspeed, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import WINDSPEED
|
2017-06-05 06:48:11 +00:00
|
|
|
try:
|
|
|
|
return float(self.data.get(WINDSPEED))
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def wind_bearing(self):
|
|
|
|
"""Return the wind bearing, or None."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import WINDAZIMUTH
|
2017-06-05 06:48:11 +00:00
|
|
|
try:
|
2017-08-16 06:07:04 +00:00
|
|
|
return int(self.data.get(WINDAZIMUTH))
|
2017-06-05 06:48:11 +00:00
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def forecast(self):
|
|
|
|
"""Return the forecast data."""
|
2019-07-28 20:08:20 +00:00
|
|
|
from buienradar.constants import FORECAST
|
2017-06-05 06:48:11 +00:00
|
|
|
return self.data.get(FORECAST)
|