Skip to content

Commit

Permalink
Météo-France platform for the weather component (#18404)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
victorcerutti authored and fabaff committed Feb 14, 2019
1 parent 801401e commit f4b2573
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 66 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
141 changes: 141 additions & 0 deletions homeassistant/components/meteo_france.py
@@ -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)
77 changes: 13 additions & 64 deletions homeassistant/components/sensor/meteo_france.py
Expand Up @@ -4,64 +4,34 @@
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)


Expand Down Expand Up @@ -96,10 +66,10 @@ def device_state_attributes(self):
},
** 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):
Expand All @@ -116,24 +86,3 @@ def update(self):
_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 changes: 112 additions & 0 deletions homeassistant/components/weather/meteo_france.py
@@ -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
4 changes: 2 additions & 2 deletions requirements_all.txt
Expand Up @@ -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
Expand Down

0 comments on commit f4b2573

Please sign in to comment.