mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 02:07:54 +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/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
|
||||
|
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
|
||||
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)
|
||||
|
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
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user