mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 23:07:09 +00:00
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:
parent
801401e9cb
commit
f4b2573c4b
@ -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
|
||||||
|
141
homeassistant/components/meteo_france.py
Normal file
141
homeassistant/components/meteo_france.py
Normal 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)
|
@ -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)
|
|
||||||
|
112
homeassistant/components/weather/meteo_france.py
Normal file
112
homeassistant/components/weather/meteo_france.py
Normal 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
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user