Météo-France platform for the weather component (#18404)

* new weather component for meteofrance

* linting

* upgrade meteofrance package version

* Update .coveragerc

* Remove updates to the weather component architecture

* Rewrite Météo-France as a component

* Update .coveragerc

* Update requirements_all.txt

* remove Weather Card option

* Update conf name

Changing conf name to something more universal for worldwide weather forecast (postal code was only relevent for France)

* Update meteofrance pypi package

* fix line too long

* Update requirements_all.txt

* prevent from calling an API endpoint if not in monitored conditions

* fix stale url and remove blank line

* Insure that all cities are unique

* rename CONF_ATTRIBUTION

* Updating data from component setup

* fix missing extra lines
This commit is contained in:
Victor Cerutti 2019-02-14 14:40:27 +01:00 committed by Fabian Affolter
parent 801401e9cb
commit f4b2573c4b
5 changed files with 270 additions and 66 deletions

View File

@ -326,6 +326,7 @@ omit =
homeassistant/components/nest/* homeassistant/components/nest/*
homeassistant/components/netatmo/* homeassistant/components/netatmo/*
homeassistant/components/netgear_lte/* homeassistant/components/netgear_lte/*
homeassistant/components/meteo_france.py
homeassistant/components/notify/aws_lambda.py homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py homeassistant/components/notify/aws_sqs.py
@ -650,6 +651,7 @@ omit =
homeassistant/components/weather/buienradar.py homeassistant/components/weather/buienradar.py
homeassistant/components/weather/darksky.py homeassistant/components/weather/darksky.py
homeassistant/components/weather/met.py homeassistant/components/weather/met.py
homeassistant/components/weather/meteo_france.py
homeassistant/components/weather/metoffice.py homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.py homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py homeassistant/components/weather/zamg.py

View File

@ -0,0 +1,141 @@
"""
Support for Meteo France weather forecast.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/meteo_france/
"""
import logging
import datetime
import voluptuous as vol
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, TEMP_CELSIUS)
from homeassistant.util import Throttle
from homeassistant.helpers.discovery import load_platform
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['meteofrance==0.3.4']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'meteo_france'
SCAN_INTERVAL = datetime.timedelta(minutes=5)
ATTRIBUTION = "Data provided by Météo-France"
CONF_CITY = 'city'
DEFAULT_WEATHER_CARD = True
DATA_METEO_FRANCE = 'data_meteo_france'
SENSOR_TYPES = {
'rain_chance': ['Rain chance', '%'],
'freeze_chance': ['Freeze chance', '%'],
'thunder_chance': ['Thunder chance', '%'],
'snow_chance': ['Snow chance', '%'],
'weather': ['Weather', None],
'wind_speed': ['Wind Speed', 'km/h'],
'next_rain': ['Next rain', 'min'],
'temperature': ['Temperature', TEMP_CELSIUS],
'uv': ['UV', None],
}
CONDITION_CLASSES = {
'clear-night': ['Nuit Claire'],
'cloudy': ['Très nuageux'],
'fog': ['Brume ou bancs de brouillard',
'Brouillard', 'Brouillard givrant'],
'hail': ['Risque de grêle'],
'lightning': ["Risque d'orages", 'Orages'],
'lightning-rainy': ['Pluie orageuses', 'Pluies orageuses',
'Averses orageuses'],
'partlycloudy': ['Ciel voilé', 'Ciel voilé nuit', 'Éclaircies'],
'pouring': ['Pluie forte'],
'rainy': ['Bruine / Pluie faible', 'Bruine', 'Pluie faible',
'Pluies éparses / Rares averses', 'Pluies éparses',
'Rares averses', 'Pluie / Averses', 'Averses', 'Pluie'],
'snowy': ['Neige / Averses de neige', 'Neige', 'Averses de neige',
'Neige forte', 'Quelques flocons'],
'snowy-rainy': ['Pluie et neige', 'Pluie verglaçante'],
'sunny': ['Ensoleillé'],
'windy': [],
'windy-variant': [],
'exceptional': [],
}
def has_all_unique_cities(value):
"""Validate that all cities are unique."""
cities = [location[CONF_CITY] for location in value]
vol.Schema(vol.Unique())(cities)
return value
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Required(CONF_CITY): cv.string,
vol.Optional(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})], has_all_unique_cities)
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Meteo-France component."""
hass.data[DATA_METEO_FRANCE] = {}
for location in config[DOMAIN]:
city = location[CONF_CITY]
from meteofrance.client import meteofranceClient, meteofranceError
try:
client = meteofranceClient(city)
except meteofranceError as exp:
_LOGGER.error(exp)
return
client.need_rain_forecast = bool(CONF_MONITORED_CONDITIONS in location
and 'next_rain' in
location[CONF_MONITORED_CONDITIONS])
hass.data[DATA_METEO_FRANCE][city] = MeteoFranceUpdater(client)
hass.data[DATA_METEO_FRANCE][city].update()
if CONF_MONITORED_CONDITIONS in location:
monitored_conditions = location[CONF_MONITORED_CONDITIONS]
load_platform(
hass,
'sensor',
DOMAIN,
{CONF_CITY: city,
CONF_MONITORED_CONDITIONS: monitored_conditions},
config)
load_platform(
hass,
'weather',
DOMAIN,
{CONF_CITY: city},
config)
return True
class MeteoFranceUpdater:
"""Update data from Meteo-France."""
def __init__(self, client):
"""Initialize the data object."""
self._client = client
def get_data(self):
"""Get the latest data from Meteo-France."""
return self._client.get_data()
@Throttle(SCAN_INTERVAL)
def update(self):
"""Get the latest data from Meteo-France."""
from meteofrance.client import meteofranceError
try:
self._client.update()
except meteofranceError as exp:
_LOGGER.error(exp)

View File

@ -4,64 +4,34 @@ Support for Meteo France raining forecast.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.meteo_france/ https://home-assistant.io/components/sensor.meteo_france/
""" """
import logging import logging
import datetime
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.components.meteo_france import (SENSOR_TYPES,
DATA_METEO_FRANCE,
CONF_CITY,
ATTRIBUTION)
from homeassistant.const import ( from homeassistant.const import (
CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION, TEMP_CELSIUS) CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['meteofrance==0.2.7']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by Meteo-France"
CONF_POSTAL_CODE = 'postal_code'
STATE_ATTR_FORECAST = '1h rain forecast' STATE_ATTR_FORECAST = '1h rain forecast'
SCAN_INTERVAL = datetime.timedelta(minutes=5)
SENSOR_TYPES = {
'rain_chance': ['Rain chance', '%'],
'freeze_chance': ['Freeze chance', '%'],
'thunder_chance': ['Thunder chance', '%'],
'snow_chance': ['Snow chance', '%'],
'weather': ['Weather', None],
'wind_speed': ['Wind Speed', 'km/h'],
'next_rain': ['Next rain', 'min'],
'temperature': ['Temperature', TEMP_CELSIUS],
'uv': ['UV', None],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_POSTAL_CODE): cv.string,
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Meteo-France sensor.""" """Set up the Meteo-France sensor."""
postal_code = config[CONF_POSTAL_CODE] if discovery_info is None:
from meteofrance.client import meteofranceClient, meteofranceError
try:
meteofrance_client = meteofranceClient(postal_code)
except meteofranceError as exp:
_LOGGER.error(exp)
return return
client = MeteoFranceUpdater(meteofrance_client) city = discovery_info[CONF_CITY]
monitored_conditions = discovery_info[CONF_MONITORED_CONDITIONS]
client = hass.data[DATA_METEO_FRANCE][city]
add_entities([MeteoFranceSensor(variable, client) add_entities([MeteoFranceSensor(variable, client)
for variable in config[CONF_MONITORED_CONDITIONS]], for variable in monitored_conditions],
True) True)
@ -96,10 +66,10 @@ class MeteoFranceSensor(Entity):
}, },
** self._data["next_rain_intervals"], ** self._data["next_rain_intervals"],
**{ **{
ATTR_ATTRIBUTION: CONF_ATTRIBUTION ATTR_ATTRIBUTION: ATTRIBUTION
} }
} }
return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION} return {ATTR_ATTRIBUTION: ATTRIBUTION}
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
@ -116,24 +86,3 @@ class MeteoFranceSensor(Entity):
_LOGGER.error("No condition `%s` for location `%s`", _LOGGER.error("No condition `%s` for location `%s`",
self._condition, self._data["name"]) self._condition, self._data["name"])
self._state = None self._state = None
class MeteoFranceUpdater:
"""Update data from Meteo-France."""
def __init__(self, client):
"""Initialize the data object."""
self._client = client
def get_data(self):
"""Get the latest data from Meteo-France."""
return self._client.get_data()
@Throttle(SCAN_INTERVAL)
def update(self):
"""Get the latest data from Meteo-France."""
from meteofrance.client import meteofranceError
try:
self._client.update()
except meteofranceError as exp:
_LOGGER.error(exp)

View File

@ -0,0 +1,112 @@
"""
Support for Meteo france weather service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/weather.meteo_france/
"""
import logging
from datetime import datetime, timedelta
from homeassistant.components.meteo_france import (DATA_METEO_FRANCE,
CONDITION_CLASSES,
CONF_CITY,
ATTRIBUTION)
from homeassistant.components.weather import (
WeatherEntity, ATTR_FORECAST_CONDITION,
ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME)
from homeassistant.const import TEMP_CELSIUS
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Meteo-France weather platform."""
if discovery_info is None:
return
city = discovery_info[CONF_CITY]
client = hass.data[DATA_METEO_FRANCE][city]
add_entities([MeteoFranceWeather(client)], True)
class MeteoFranceWeather(WeatherEntity):
"""Representation of a weather condition."""
def __init__(self, client):
"""Initialise the platform with a data instance and station name."""
self._client = client
self._data = {}
def update(self):
"""Update current conditions."""
self._client.update()
self._data = self._client.get_data()
@property
def name(self):
"""Return the name of the sensor."""
return self._data["name"]
@property
def condition(self):
"""Return the current condition."""
return self.format_condition(self._data["weather"])
@property
def temperature(self):
"""Return the platform temperature."""
return self._data["temperature"]
@property
def humidity(self):
"""Return the platform temperature."""
return None
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def wind_speed(self):
"""Return the wind speed."""
return self._data["wind_speed"]
@property
def wind_bearing(self):
"""Return the wind bearing."""
return self._data["wind_bearing"]
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
@property
def forecast(self):
"""Return the forecast."""
reftime = datetime.now().replace(hour=12, minute=00)
reftime += timedelta(hours=24)
forecast_data = []
for key in self._data["forecast"]:
value = self._data["forecast"][key]
data_dict = {
ATTR_FORECAST_TIME: reftime.isoformat(),
ATTR_FORECAST_TEMP: int(value['max_temp']),
ATTR_FORECAST_TEMP_LOW: int(value['min_temp']),
ATTR_FORECAST_CONDITION:
self.format_condition(value["weather"])
}
reftime = reftime + timedelta(hours=24)
forecast_data.append(data_dict)
return forecast_data
@staticmethod
def format_condition(condition):
"""Return condition from dict CONDITION_CLASSES."""
for key, value in CONDITION_CLASSES.items():
if condition in value:
return key
return condition

View File

@ -681,8 +681,8 @@ mbddns==0.1.2
# homeassistant.components.notify.message_bird # homeassistant.components.notify.message_bird
messagebird==1.2.0 messagebird==1.2.0
# homeassistant.components.sensor.meteo_france # homeassistant.components.meteo_france
meteofrance==0.2.7 meteofrance==0.3.4
# homeassistant.components.sensor.mfi # homeassistant.components.sensor.mfi
# homeassistant.components.switch.mfi # homeassistant.components.switch.mfi