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/netatmo/*
homeassistant/components/netgear_lte/*
homeassistant/components/meteo_france.py
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
@ -650,6 +651,7 @@ omit =
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/darksky.py
homeassistant/components/weather/met.py
homeassistant/components/weather/meteo_france.py
homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.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
https://home-assistant.io/components/sensor.meteo_france/
"""
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 (
CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION, TEMP_CELSIUS)
CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION)
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__)
CONF_ATTRIBUTION = "Data provided by Meteo-France"
CONF_POSTAL_CODE = 'postal_code'
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):
"""Set up the Meteo-France sensor."""
postal_code = config[CONF_POSTAL_CODE]
from meteofrance.client import meteofranceClient, meteofranceError
try:
meteofrance_client = meteofranceClient(postal_code)
except meteofranceError as exp:
_LOGGER.error(exp)
if discovery_info is None:
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)
for variable in config[CONF_MONITORED_CONDITIONS]],
for variable in monitored_conditions],
True)
@ -96,10 +66,10 @@ class MeteoFranceSensor(Entity):
},
** self._data["next_rain_intervals"],
**{
ATTR_ATTRIBUTION: CONF_ATTRIBUTION
ATTR_ATTRIBUTION: ATTRIBUTION
}
}
return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION}
return {ATTR_ATTRIBUTION: ATTRIBUTION}
@property
def unit_of_measurement(self):
@ -116,24 +86,3 @@ class MeteoFranceSensor(Entity):
_LOGGER.error("No condition `%s` for location `%s`",
self._condition, self._data["name"])
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
messagebird==1.2.0
# homeassistant.components.sensor.meteo_france
meteofrance==0.2.7
# homeassistant.components.meteo_france
meteofrance==0.3.4
# homeassistant.components.sensor.mfi
# homeassistant.components.switch.mfi