From f30821e0a8df513155101d58d94db64900919782 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Fri, 12 Jan 2024 22:30:38 +0100 Subject: [PATCH 1/6] Prepare support for weather warning --- custom_components/irm_kmi/binary_sensor.py | 48 ++++++++++++++++++++++ custom_components/irm_kmi/const.py | 2 +- custom_components/irm_kmi/coordinator.py | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 custom_components/irm_kmi/binary_sensor.py diff --git a/custom_components/irm_kmi/binary_sensor.py b/custom_components/irm_kmi/binary_sensor.py new file mode 100644 index 0000000..531c35c --- /dev/null +++ b/custom_components/irm_kmi/binary_sensor.py @@ -0,0 +1,48 @@ +"""Sensor to signal weather warning from the IRM KMI""" + +import logging + +from homeassistant.components import binary_sensor +from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from custom_components.irm_kmi import IrmKmiCoordinator, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback): + """Set up the binary platform""" + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities([IrmKmiWarning(coordinator, entry)]) + + +class IrmKmiWarning(CoordinatorEntity, BinarySensorEntity): + """Representation of a weather warning binary sensor""" + + def __init__(self, + coordinator: IrmKmiCoordinator, + entry: ConfigEntry + ) -> None: + _LOGGER.info(f"{entry.entry_id}, {entry.title}") + super().__init__(coordinator) + BinarySensorEntity.__init__(self) + self._attr_device_class = BinarySensorDeviceClass.SAFETY + self._attr_unique_id = entry.entry_id + self.entity_id = binary_sensor.ENTITY_ID_FORMAT.format(f"weather_warning_{str(entry.title).lower()}") + self._attr_name = f"Warning {entry.title}" + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={(DOMAIN, entry.entry_id)}, + manufacturer="IRM KMI", + name=f"Warning {entry.title}" + ) + + @property + def is_on(self) -> bool | None: + # TODO return a real value but first, change implementation of coordinator to expose the data + return True diff --git a/custom_components/irm_kmi/const.py b/custom_components/irm_kmi/const.py index 862438c..c710614 100644 --- a/custom_components/irm_kmi/const.py +++ b/custom_components/irm_kmi/const.py @@ -15,7 +15,7 @@ from homeassistant.components.weather import (ATTR_CONDITION_CLEAR_NIGHT, from homeassistant.const import Platform DOMAIN: Final = 'irm_kmi' -PLATFORMS: Final = [Platform.WEATHER, Platform.CAMERA] +PLATFORMS: Final = [Platform.WEATHER, Platform.CAMERA, Platform.BINARY_SENSOR] CONFIG_FLOW_VERSION = 3 OUT_OF_BENELUX: Final = ["außerhalb der Benelux (Brussels)", diff --git a/custom_components/irm_kmi/coordinator.py b/custom_components/irm_kmi/coordinator.py index a70d2b5..f326fe7 100644 --- a/custom_components/irm_kmi/coordinator.py +++ b/custom_components/irm_kmi/coordinator.py @@ -39,7 +39,7 @@ class IrmKmiCoordinator(DataUpdateCoordinator): # Name of the data. For logging purposes. name="IRM KMI weather", # Polling interval. Will only be polled if there are subscribers. - update_interval=timedelta(seconds=15), + update_interval=timedelta(minutes=7), ) self._api_client = IrmKmiApiClient(session=async_get_clientsession(hass)) self._zone = get_config_value(entry, CONF_ZONE) From bd26a99b0c8c456017c018f5cac3c2112e42e58c Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 21:54:44 +0100 Subject: [PATCH 2/6] Implement binary sensor for weather warning --- custom_components/irm_kmi/binary_sensor.py | 28 +- custom_components/irm_kmi/const.py | 13 + custom_components/irm_kmi/coordinator.py | 44 +- custom_components/irm_kmi/data.py | 12 + tests/conftest.py | 4 + tests/fixtures/be_forecast_warning.json | 1668 ++++++++++++++++++++ tests/test_binary_sensor.py | 27 + tests/test_config_flow.py | 1 - tests/test_coordinator.py | 33 +- 9 files changed, 1811 insertions(+), 19 deletions(-) create mode 100644 tests/fixtures/be_forecast_warning.json create mode 100644 tests/test_binary_sensor.py diff --git a/custom_components/irm_kmi/binary_sensor.py b/custom_components/irm_kmi/binary_sensor.py index 531c35c..5442512 100644 --- a/custom_components/irm_kmi/binary_sensor.py +++ b/custom_components/irm_kmi/binary_sensor.py @@ -1,16 +1,18 @@ """Sensor to signal weather warning from the IRM KMI""" - +import datetime import logging +import pytz from homeassistant.components import binary_sensor -from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass +from homeassistant.components.binary_sensor import (BinarySensorDeviceClass, + BinarySensorEntity) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType +from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from custom_components.irm_kmi import IrmKmiCoordinator, DOMAIN +from custom_components.irm_kmi import DOMAIN, IrmKmiCoordinator _LOGGER = logging.getLogger(__name__) @@ -28,7 +30,6 @@ class IrmKmiWarning(CoordinatorEntity, BinarySensorEntity): coordinator: IrmKmiCoordinator, entry: ConfigEntry ) -> None: - _LOGGER.info(f"{entry.entry_id}, {entry.title}") super().__init__(coordinator) BinarySensorEntity.__init__(self) self._attr_device_class = BinarySensorDeviceClass.SAFETY @@ -44,5 +45,18 @@ class IrmKmiWarning(CoordinatorEntity, BinarySensorEntity): @property def is_on(self) -> bool | None: - # TODO return a real value but first, change implementation of coordinator to expose the data - return True + if self.coordinator.data.get('warnings') is None: + return False + + now = datetime.datetime.now(tz=pytz.timezone(self.hass.config.time_zone)) + for item in self.coordinator.data.get('warnings'): + if item.get('starts_at') < now < item.get('ends_at'): + return True + + return False + + @property + def extra_state_attributes(self) -> dict: + """Return the camera state attributes.""" + attrs = {"warnings": self.coordinator.data.get('warnings', [])} + return attrs diff --git a/custom_components/irm_kmi/const.py b/custom_components/irm_kmi/const.py index c710614..07ef9f0 100644 --- a/custom_components/irm_kmi/const.py +++ b/custom_components/irm_kmi/const.py @@ -121,3 +121,16 @@ IRM_KMI_TO_HA_CONDITION_MAP: Final = { (27, 'd'): ATTR_CONDITION_EXCEPTIONAL, (27, 'n'): ATTR_CONDITION_EXCEPTIONAL } + +MAP_WARNING_ID_TO_SLUG: Final = { + 0: 'wind', + 1: 'rain', + 2: 'ice_or_snow', + 3: 'thunder', + 7: 'fog', + 9: 'cold', + 12: 'thunder_wind_rain', + 13: 'thunderstorm_strong_gusts', + 14: 'thunderstorm_large_rainfall', + 15: 'storm_surge', + 17: 'coldspell'} diff --git a/custom_components/irm_kmi/coordinator.py b/custom_components/irm_kmi/coordinator.py index f326fe7..f7cfd39 100644 --- a/custom_components/irm_kmi/coordinator.py +++ b/custom_components/irm_kmi/coordinator.py @@ -18,10 +18,11 @@ from homeassistant.helpers.update_coordinator import (DataUpdateCoordinator, from .api import IrmKmiApiClient, IrmKmiApiError from .const import CONF_DARK_MODE, CONF_STYLE, DOMAIN from .const import IRM_KMI_TO_HA_CONDITION_MAP as CDT_MAP -from .const import (LANGS, OPTION_STYLE_SATELLITE, OUT_OF_BENELUX, - STYLE_TO_PARAM_MAP) +from .const import LANGS +from .const import MAP_WARNING_ID_TO_SLUG as SLUG_MAP +from .const import OPTION_STYLE_SATELLITE, OUT_OF_BENELUX, STYLE_TO_PARAM_MAP from .data import (AnimationFrameData, CurrentWeatherData, IrmKmiForecast, - ProcessedCoordinatorData, RadarAnimationData) + ProcessedCoordinatorData, RadarAnimationData, WarningData) from .rain_graph import RainGraph from .utils import disable_from_config, get_config_value @@ -131,7 +132,8 @@ class IrmKmiCoordinator(DataUpdateCoordinator): current_weather=IrmKmiCoordinator.current_weather_from_data(api_data), daily_forecast=IrmKmiCoordinator.daily_list_to_forecast(api_data.get('for', {}).get('daily')), hourly_forecast=IrmKmiCoordinator.hourly_list_to_forecast(api_data.get('for', {}).get('hourly')), - animation=await self._async_animation_data(api_data=api_data) + animation=await self._async_animation_data(api_data=api_data), + warnings=self.warnings_from_data(api_data.get('for', {}).get('warning')) ) async def download_images_from_api(self, @@ -344,3 +346,37 @@ class IrmKmiCoordinator(DataUpdateCoordinator): return RainGraph(radar_animation, image_path, bg_size, dark_mode=self._dark_mode, tz=self.hass.config.time_zone) + + def warnings_from_data(self, warning_data: list | None) -> List[WarningData] | None: + """Create a list of warning data instances based on the api data""" + if warning_data is None or not isinstance(warning_data, list) or len(warning_data) == 0: + return None + + result = list() + for data in warning_data: + try: + warning_id = int(data.get('warningType', {}).get('id')) + start = datetime.fromisoformat(data.get('fromTimestamp', None)) + end = datetime.fromisoformat(data.get('toTimestamp', None)) + except TypeError | ValueError: + # Without this data, the warning is useless + continue + + try: + level = int(data.get('warningLevel')) + except TypeError: + level = None + + result.append( + WarningData( + slug=SLUG_MAP.get(warning_id, 'unknown'), + id=warning_id, + level=level, + friendly_name=data.get('warningType', {}).get('name', {}).get(self.hass.config.language), + text=data.get('text', {}).get(self.hass.config.language), + starts_at=start, + ends_at=end + ) + ) + + return result if len(result) > 0 else None diff --git a/custom_components/irm_kmi/data.py b/custom_components/irm_kmi/data.py index 0763e54..606898f 100644 --- a/custom_components/irm_kmi/data.py +++ b/custom_components/irm_kmi/data.py @@ -45,9 +45,21 @@ class RadarAnimationData(TypedDict, total=False): svg_animated: bytes | None +class WarningData(TypedDict, total=False): + """Holds data about a specific warning""" + slug: str + id: int + level: int + friendly_name: str + text: str + starts_at: datetime + ends_at: datetime + + class ProcessedCoordinatorData(TypedDict, total=False): """Data class that will be exposed to the entities consuming data from an IrmKmiCoordinator""" current_weather: CurrentWeatherData hourly_forecast: List[Forecast] | None daily_forecast: List[IrmKmiForecast] | None animation: RadarAnimationData + warnings: List[WarningData] | None diff --git a/tests/conftest.py b/tests/conftest.py index 4cdf4a3..0c25f46 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,10 @@ from custom_components.irm_kmi.const import ( OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD) +def get_api_data(fixture: str) -> dict: + return json.loads(load_fixture(fixture)) + + async def patched(url: str, params: dict | None = None) -> bytes: if "cdn.knmi.nl" in url: file_name = "tests/fixtures/clouds_nl.png" diff --git a/tests/fixtures/be_forecast_warning.json b/tests/fixtures/be_forecast_warning.json new file mode 100644 index 0000000..ac3412d --- /dev/null +++ b/tests/fixtures/be_forecast_warning.json @@ -0,0 +1,1668 @@ + { + "cityName": "Floreffe", + "country": "BE", + "obs": { + "temp": -2, + "timestamp": "2024-01-12T10:10:00+01:00", + "ww": 27, + "dayNight": "d" + }, + "for": { + "daily": [ + { + "dayName": { + "fr": "Vendredi", + "nl": "Vrijdag", + "en": "Friday", + "de": "Freitag" + }, + "period": "1", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Vandaag is het meestal grijs met veel lage wolken en vooral vanochtend kans op lokale mistbanken die kunnen aanvriezen over de oostelijke landshelft. Het blijft overwegend droog, maar plaatselijk kan er nog steeds wat motregen vallen, die vooral dan in Hoog-België mogelijk aanvriest. De maxima liggen tussen -1 en +1 graad ten zuiden van Samber en Maas, rond 5 à 6 graden aan de kust en tussen 1 en 4 graden in de andere streken. De wind waait zwak tot soms matig, aan zee matig, uit noordnoordoostelijke of uit veranderlijke richtingen.", + "fr": "Aujourd'hui, le temps sera gris avec de nombreux nuages bas; surtout ce matin, il y aura risque de bancs de brouillard givrant, principalement sur la moitié est du pays. Le temps restera souvent sec, mais quelques bruines verglaçantes seront toujours possibles par endroits, surtout en Haute-Belgique. Les maxima oscilleront entre -1 et +1 degré au sud du sillon Sambre-et-Meuse, entre 1 et 4 degrés en plaine et autour de 5 à 6 degrés à la mer. Le vent sera faible à modéré, modéré au littoral, de secteur nord-nord-est ou de directions variées." + }, + "dawnRiseSeconds": "31260", + "dawnSetSeconds": "61260", + "tempMin": null, + "tempMax": 1, + "ww1": 15, + "ww2": null, + "wwevol": null, + "ff1": 2, + "ff2": 1, + "ffevol": 0, + "dd": 0, + "ddText": { + "fr": "VAR", + "nl": "VER", + "en": "VAR", + "de": "VAR" + }, + "wind": { + "speed": 1, + "peakSpeed": null, + "dir": 0, + "dirText": { + "fr": "VAR", + "nl": "VER", + "en": "VAR", + "de": "VAR" + } + }, + "precipChance": 0, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Cette nuit", + "nl": "Vannacht", + "en": "Tonight", + "de": "heute abend" + }, + "period": "2", + "day_night": "0", + "dayNight": "n", + "text": { + "nl": "Vanavond en volgende nacht blijft het overwegend zwaarbewolkt. Lokaal kan er nog steeds wat motregen vallen die vooral over de zuidoostelijke landshelft kan aanvriezen. Er is kans op nevel of plaatselijke (aanvriezende) mist. De minima schommelen tussen -1 en -4 graden in de Ardennen, rond het vriespunt in het centrum en +3 graden aan zee. De wind is meestal zwak en veranderlijk en op het einde van de nacht soms matig uit zuidwest.", + "fr": "Ce soir et la nuit prochaine, le temps restera souvent très nuageux. Localement, il peut encore y avoir de la bruine qui pourrait être verglaçante, principalement sur la moitié sud-est du pays. Risque de brume ou localement de brouillard (givrant). Les minima varieront entre -1 et -4 degrés en Ardenne, autour de 0 degré dans le centre et jusqu'à +3 degrés au littoral. Le vent sera faible et variable, en fin de nuit il deviendra modéré de secteur sud-ouest." + }, + "dawnRiseSeconds": "31260", + "dawnSetSeconds": "61260", + "tempMin": -1, + "tempMax": null, + "ww1": 15, + "ww2": null, + "wwevol": null, + "ff1": 1, + "ff2": 2, + "ffevol": 0, + "dd": 0, + "ddText": { + "fr": "VAR", + "nl": "VER", + "en": "VAR", + "de": "VAR" + }, + "wind": { + "speed": 1, + "peakSpeed": null, + "dir": 0, + "dirText": { + "fr": "VAR", + "nl": "VER", + "en": "VAR", + "de": "VAR" + } + }, + "precipChance": 5, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Samedi", + "nl": "Zaterdag", + "en": "Saturday", + "de": "Samstag" + }, + "period": "3", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Zaterdag wordt grijs met 's ochtends lokaal aanvriezende nevel of mist, hoofdzakelijk dan in de Ardennen. Het blijft grotendeels droog met hier en daar wel wat gedruppel of enkele sneeuwvlokjes. De maxima schommelen tussen -1 graad in de Hoge Venen, 4 graden in het centrum en +5 of +6 graden aan zee. De wind waait matig uit west tot zuidwest.", + "fr": "Samedi, la journée sera grise avec en matinée localement des brumes et brouillards givrants, surtout en Ardenne. Le temps devrait rester sec à quelques gouttes\/flocons près. Les maxima se situeront entre -1 degré en Hautes-Fagnes et +5 ou +6 degrés à la mer, avec des valeurs autour de +4 degrés dans le centre du pays. Le vent sera modéré de secteur ouest à sud-ouest." + }, + "dawnRiseSeconds": "31200", + "dawnSetSeconds": "61320", + "tempMin": -1, + "tempMax": 4, + "ww1": 15, + "ww2": null, + "wwevol": null, + "ff1": 2, + "ff2": 3, + "ffevol": 0, + "dd": 67, + "ddText": { + "fr": "OSO", + "nl": "WZW", + "en": "WSW", + "de": "WSW" + }, + "wind": { + "speed": 12, + "peakSpeed": null, + "dir": 67, + "dirText": { + "fr": "OSO", + "nl": "WZW", + "en": "WSW", + "de": "WSW" + } + }, + "precipChance": 0, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Dimanche", + "nl": "Zondag", + "en": "Sunday", + "de": "Sonntag" + }, + "period": "5", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Zondagochtend start opnieuw grijs met vooral in de Ardennen aanvriezende mist en nevel. In de namiddag vergroot de kans op enkele opklaringen, maar is er ook kans op enkele winterse buien, vooral dan in Hoog-België. De maxima schommelen tussen -1 en +6 graden. De wind waait matig uit het zuidwesten tot het westen in het binnenland en vrij krachtig uit het noordwesten aan zee.", + "fr": "Dimanche matin aussi, le temps sera gris avec surtout en Ardenne des brumes et brouillards givrants. L'après-midi, des éclaircies devraient percer la couverture nuageuse. Par contre, on notera un risque de quelques averses, à caractère hivernal principalement en haute Belgique. Les maxima varieront de -1 à +6 degrés. Le vent sera modéré de secteur sud-ouest à ouest dans l’intérieur du pays et plutôt assez fort de nord-ouest à la mer." + }, + "dawnRiseSeconds": "31200", + "dawnSetSeconds": "61440", + "tempMin": 1, + "tempMax": 2, + "ww1": 15, + "ww2": null, + "wwevol": null, + "ff1": 3, + "ff2": null, + "ffevol": null, + "dd": 45, + "ddText": { + "fr": "SO", + "nl": "ZW", + "en": "SW", + "de": "SW" + }, + "wind": { + "speed": 12, + "peakSpeed": null, + "dir": 45, + "dirText": { + "fr": "SO", + "nl": "ZW", + "en": "SW", + "de": "SW" + } + }, + "precipChance": 0, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Lundi", + "nl": "Maandag", + "en": "Monday", + "de": "Montag" + }, + "period": "7", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Maandag is het weerbeeld wisselvalliger met opklaringen en wolken. Er vallen enkele buien, vooral dan over de noordoostelijke landshelft. Deze buien kunnen een winters karakter krijgen en zullen in de Ardennen als sneeuw vallen. We halen maxima van -2 tot +5 graden bij een matige westenwind. Aan zee is de wind vrij krachtig uit noordwest.", + "fr": "Lundi, le temps sera plus variable avec une alternance d'éclaircies et de passages nuageux, parfois porteurs d'averses qui toucheront essentiellement la moitié nord-est du pays. Celles-ci pourront prendre un caractère hivernal, même en plaine, et tomberont sous forme de neige en Ardenne. Les maxima se situeront entre -2 et +5 degrés, sous un vent modéré d'ouest dans les terres et assez fort de nord-ouest au littoral." + }, + "dawnRiseSeconds": "31140", + "dawnSetSeconds": "61500", + "tempMin": 0, + "tempMax": 2, + "ww1": 3, + "ww2": null, + "wwevol": null, + "ff1": 4, + "ff2": 3, + "ffevol": 1, + "dd": 90, + "ddText": { + "fr": "O", + "nl": "W", + "en": "W", + "de": "W" + }, + "wind": { + "speed": 20, + "peakSpeed": null, + "dir": 90, + "dirText": { + "fr": "O", + "nl": "W", + "en": "W", + "de": "W" + } + }, + "precipChance": 0, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Mardi", + "nl": "Dinsdag", + "en": "Tuesday", + "de": "Dienstag" + }, + "period": "9", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Dinsdag wordt het rustig, droog en krijgen we meer zon. In de Ardennen is het 's ochtends tijdelijk grijs. Het is vrij koud met in het centrum maxima rond 1 of 2 graden. In de Ardennen schommelen de maxima tussen -3 en 0 graden. Er waait een zwakke tot matige wind uit zuidwest, krimpend naar zuid.", + "fr": "Mardi, le temps sera calme, sec et plus ensoleillé, une fois la grisaille matinale dissipée en Ardenne. Il fera assez froid ; après des gelées nocturnes généralisées, le mercure ne remontera que vers des valeurs de +1 ou +2 degrés dans le centre du pays. En Ardenne, les maxima se situeront entre -3 et 0 degré. Le vent sera heureusement faible à modéré, revenant du sud-ouest au sud." + }, + "dawnRiseSeconds": "31080", + "dawnSetSeconds": "61620", + "tempMin": -4, + "tempMax": 1, + "ww1": 1, + "ww2": 3, + "wwevol": 0, + "ff1": 3, + "ff2": null, + "ffevol": null, + "dd": 45, + "ddText": { + "fr": "SO", + "nl": "ZW", + "en": "SW", + "de": "SW" + }, + "wind": { + "speed": 12, + "peakSpeed": null, + "dir": 45, + "dirText": { + "fr": "SO", + "nl": "ZW", + "en": "SW", + "de": "SW" + } + }, + "precipChance": 0, + "precipQuantity": "0" + }, + { + "dayName": { + "fr": "Mercredi", + "nl": "Woensdag", + "en": "Wednesday", + "de": "Mittwoch" + }, + "period": "11", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Woensdag en donderdag zal een actieve storing onze streken beïnvloeden. Aangezien de grens met de koude lucht dichtbij ligt, bestaat er nog veel onzekerheid over het neerslagtype, maar winterse neerslag of sneeuw zijn mogelijk.", + "fr": "Mercredi et jeudi, une active perturbation affectera nos régions. La limite avec l'air froid n'étant pas bien loin, il y a encore beaucoup d'incertitudes sur le type de précipitations mais celles-ci pourraient prendre un caractère hivernal, voire tomber sous forme de neige, même en plaine. Cette situation délicate est donc à suivre." + }, + "dawnRiseSeconds": "31020", + "dawnSetSeconds": "61680", + "tempMin": -3, + "tempMax": 2, + "ww1": 15, + "ww2": 18, + "wwevol": 0, + "ff1": 3, + "ff2": null, + "ffevol": null, + "dd": 315, + "ddText": { + "fr": "SE", + "nl": "ZO", + "en": "SE", + "de": "SO" + }, + "wind": { + "speed": 12, + "peakSpeed": null, + "dir": 315, + "dirText": { + "fr": "SE", + "nl": "ZO", + "en": "SE", + "de": "SO" + } + }, + "precipChance": 100, + "precipQuantity": "1" + }, + { + "dayName": { + "fr": "Jeudi", + "nl": "Donderdag", + "en": "Thursday", + "de": "Donnerstag" + }, + "period": "13", + "day_night": "1", + "dayNight": "d", + "text": { + "nl": "Woensdag en donderdag zal een actieve storing onze streken beïnvloeden. Aangezien de grens met de koude lucht dichtbij ligt, bestaat er nog veel onzekerheid over het neerslagtype, maar winterse neerslag of sneeuw zijn mogelijk.", + "fr": "Mercredi et jeudi, une active perturbation affectera nos régions. La limite avec l'air froid n'étant pas bien loin, il y a encore beaucoup d'incertitudes sur le type de précipitations mais celles-ci pourraient prendre un caractère hivernal, voire tomber sous forme de neige, même en plaine. Cette situation délicate est donc à suivre." + }, + "dawnRiseSeconds": "30960", + "dawnSetSeconds": "61800", + "tempMin": -1, + "tempMax": 2, + "ww1": 18, + "ww2": null, + "wwevol": null, + "ff1": 3, + "ff2": null, + "ffevol": null, + "dd": 67, + "ddText": { + "fr": "OSO", + "nl": "WZW", + "en": "WSW", + "de": "WSW" + }, + "wind": { + "speed": 12, + "peakSpeed": null, + "dir": 67, + "dirText": { + "fr": "OSO", + "nl": "WZW", + "en": "WSW", + "de": "WSW" + } + }, + "precipChance": 100, + "precipQuantity": "1" + } + ], + "showWarningTab": true, + "graph": { + "svg": [ + { + "url": { + "nl": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tx&l=nl&k=4a343a758cad373cfd3cb0f34f47e3e4", + "fr": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tx&l=fr&k=4a343a758cad373cfd3cb0f34f47e3e4", + "en": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tx&l=en&k=4a343a758cad373cfd3cb0f34f47e3e4", + "de": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tx&l=de&k=4a343a758cad373cfd3cb0f34f47e3e4" + }, + "ratio": 1.3638709677419354 + }, + { + "url": { + "nl": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tn&l=nl&k=4a343a758cad373cfd3cb0f34f47e3e4", + "fr": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tn&l=fr&k=4a343a758cad373cfd3cb0f34f47e3e4", + "en": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tn&l=en&k=4a343a758cad373cfd3cb0f34f47e3e4", + "de": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=tn&l=de&k=4a343a758cad373cfd3cb0f34f47e3e4" + }, + "ratio": 1.3638709677419354 + }, + { + "url": { + "nl": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=rr&l=nl&k=4a343a758cad373cfd3cb0f34f47e3e4", + "fr": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=rr&l=fr&k=4a343a758cad373cfd3cb0f34f47e3e4", + "en": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=rr&l=en&k=4a343a758cad373cfd3cb0f34f47e3e4", + "de": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=rr&l=de&k=4a343a758cad373cfd3cb0f34f47e3e4" + }, + "ratio": 1.3638709677419354 + } + ] + }, + "hourly": [ + { + "hour": "10", + "temp": -1, + "ww": "3", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1034, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 203, + "windDirectionText": { + "nl": "NNO", + "fr": "NNE", + "en": "NNE", + "de": "NNO" + }, + "dayNight": "d" + }, + { + "hour": "11", + "temp": -1, + "ww": "3", + "precipChance": "0", + "precipQuantity": 0.002, + "pressure": 1034, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 203, + "windDirectionText": { + "nl": "NNO", + "fr": "NNE", + "en": "NNE", + "de": "NNO" + }, + "dayNight": "d" + }, + { + "hour": "12", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1034, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 248, + "windDirectionText": { + "nl": "ONO", + "fr": "ENE", + "en": "ENE", + "de": "ONO" + }, + "dayNight": "d" + }, + { + "hour": "13", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.018000000000000002, + "pressure": 1033, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 225, + "windDirectionText": { + "nl": "NO", + "fr": "NE", + "en": "NE", + "de": "NO" + }, + "dayNight": "d" + }, + { + "hour": "14", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1033, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 248, + "windDirectionText": { + "nl": "ONO", + "fr": "ENE", + "en": "ENE", + "de": "ONO" + }, + "dayNight": "d" + }, + { + "hour": "15", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.046, + "pressure": 1033, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 180, + "windDirectionText": { + "nl": "N", + "fr": "N", + "en": "N", + "de": "N" + }, + "dayNight": "d" + }, + { + "hour": "16", + "temp": 1, + "ww": "14", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1033, + "windSpeedKm": 0, + "windPeakSpeedKm": null, + "windDirection": 158, + "windDirectionText": { + "nl": "NNW", + "fr": "NNO", + "en": "NNW", + "de": "NNW" + }, + "dayNight": "d" + }, + { + "hour": "17", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.003, + "pressure": 1032, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 113, + "windDirectionText": { + "nl": "WNW", + "fr": "ONO", + "en": "WNW", + "de": "WNW" + }, + "dayNight": "d" + }, + { + "hour": "18", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.059, + "pressure": 1033, + "windSpeedKm": 0, + "windPeakSpeedKm": null, + "windDirection": 203, + "windDirectionText": { + "nl": "NNO", + "fr": "NNE", + "en": "NNE", + "de": "NNO" + }, + "dayNight": "n" + }, + { + "hour": "19", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.099, + "pressure": 1032, + "windSpeedKm": 0, + "windPeakSpeedKm": null, + "windDirection": 0, + "windDirectionText": { + "nl": "Z", + "fr": "S", + "en": "S", + "de": "S" + }, + "dayNight": "n" + }, + { + "hour": "20", + "temp": 0, + "ww": "15", + "precipChance": "30", + "precipQuantity": 0.10300000000000001, + "pressure": 1032, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 23, + "windDirectionText": { + "nl": "ZZW", + "fr": "SSO", + "en": "SSW", + "de": "SSW" + }, + "dayNight": "n" + }, + { + "hour": "21", + "temp": 0, + "ww": "22", + "precipChance": "30", + "precipQuantity": 0.22, + "pressure": 1032, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 0, + "windDirectionText": { + "nl": "Z", + "fr": "S", + "en": "S", + "de": "S" + }, + "dayNight": "n" + }, + { + "hour": "22", + "temp": 0, + "ww": "15", + "precipChance": "30", + "precipQuantity": 0.096, + "pressure": 1032, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 113, + "windDirectionText": { + "nl": "WNW", + "fr": "ONO", + "en": "WNW", + "de": "WNW" + }, + "dayNight": "n" + }, + { + "hour": "23", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.018000000000000002, + "pressure": 1032, + "windSpeedKm": 0, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "00", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1031, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n", + "dateShow": "13\/01", + "dateShowLocalized": { + "nl": "Zat.", + "fr": "Sam.", + "en": "Sat.", + "de": "Sam." + } + }, + { + "hour": "01", + "temp": -1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1031, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n" + }, + { + "hour": "02", + "temp": -1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1030, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n" + }, + { + "hour": "03", + "temp": -1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1030, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "04", + "temp": -1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1029, + "windSpeedKm": 5, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "05", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1029, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n" + }, + { + "hour": "06", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1028, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "07", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1028, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "08", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.02, + "pressure": 1028, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 90, + "windDirectionText": { + "nl": "W", + "fr": "O", + "en": "W", + "de": "W" + }, + "dayNight": "n" + }, + { + "hour": "09", + "temp": 0, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.03, + "pressure": 1028, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "10", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1027, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "11", + "temp": 2, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1027, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "12", + "temp": 3, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1027, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "13", + "temp": 3, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1026, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "14", + "temp": 3, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1025, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "15", + "temp": 4, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1024, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 90, + "windDirectionText": { + "nl": "W", + "fr": "O", + "en": "W", + "de": "W" + }, + "dayNight": "d" + }, + { + "hour": "16", + "temp": 3, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1024, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 90, + "windDirectionText": { + "nl": "W", + "fr": "O", + "en": "W", + "de": "W" + }, + "dayNight": "d" + }, + { + "hour": "17", + "temp": 2, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1024, + "windSpeedKm": 10, + "windPeakSpeedKm": null, + "windDirection": 90, + "windDirectionText": { + "nl": "W", + "fr": "O", + "en": "W", + "de": "W" + }, + "dayNight": "d" + }, + { + "hour": "18", + "temp": 2, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1024, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "19", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1023, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "20", + "temp": 2, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1023, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "21", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1022, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "22", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1022, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "23", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.02, + "pressure": 1022, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "00", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1021, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n", + "dateShow": "14\/01", + "dateShowLocalized": { + "nl": "Zon.", + "fr": "Dim.", + "en": "Sun.", + "de": "Son." + } + }, + { + "hour": "01", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1020, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "02", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1020, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "03", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1019, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "04", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0, + "pressure": 1019, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "05", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.01, + "pressure": 1018, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "06", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.05, + "pressure": 1017, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "n" + }, + { + "hour": "07", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.02, + "pressure": 1017, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n" + }, + { + "hour": "08", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.04, + "pressure": 1016, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "n" + }, + { + "hour": "09", + "temp": 1, + "ww": "15", + "precipChance": "0", + "precipQuantity": 0.02, + "pressure": 1016, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 68, + "windDirectionText": { + "nl": "WZW", + "fr": "OSO", + "en": "WSW", + "de": "WSW" + }, + "dayNight": "d" + }, + { + "hour": "10", + "temp": 1, + "ww": "15", + "precipChance": "20", + "precipQuantity": 0.01, + "pressure": 1015, + "windSpeedKm": 15, + "windPeakSpeedKm": null, + "windDirection": 45, + "windDirectionText": { + "nl": "ZW", + "fr": "SO", + "en": "SW", + "de": "SW" + }, + "dayNight": "d" + } + ], + "warning": [ + { + "icon_country": "BE", + "warningType": { + "id": "7", + "name": { + "fr": "Brouillard", + "nl": "Mist", + "en": "Fog", + "de": "Nebel" + } + }, + "warningLevel": "1", + "text": { + "fr": "Ce matin, des bancs de brouillard givrant pourront limiter la visibilité à moins de 200m, principalement dans la moitié est du pays.", + "nl": "Vanochtend kunnen vooral over de oostelijke landshelft aanvriezende mistbanken het zicht tot minder dan 200 m belemmeren.", + "en": "Local fog with a visibility of maximal 500 m is expected in at least half of the province, but over a smaller area the visibility may be less than 200 m. Keep sufficient distance with the car and adjust your speed. Be careful.", + "de": "Es wird örtlich Nebel mit Sichtweiten unter 500 m erwartet, aber auf kleineren Flächen kann die Sichtweite weniger als 200 m betragen. Halten Sie Abstand und reduzieren Sie Ihre Fahrgeschwindigkeit und seien Sie vorsichtig." + }, + "fromTimestamp": "2024-01-12T07:00:00+01:00", + "toTimestamp": "2024-01-12T12:00:00+01:00" + }, + { + "icon_country": "BE", + "warningType": { + "id": "2", + "name": { + "fr": "Conditions glissantes", + "nl": "Gladheid", + "en": "Ice or snow", + "de": "Glätte" + } + }, + "warningLevel": "1", + "text": { + "fr": "Ce matin, il y aura principalement sur la moitié est du pays risque de bancs de brouillard givrant et donc de dépôt de givre. Nous attendons encore quelques bruines verglaçantes par endroits. Sur l'ouest, les températures seront déjà plus élevées, y réduisant sensiblement le risque de conditions glissantes.\n", + "nl": "Vanochtend is er kans op rijmplekken door aanvriezende mistbanken, hoofdzakelijk dan over de oostelijke landshelft. Er kan lokaal nog wat lichte motregen vallen die kan aanvriezen. Over het westen is het al zachter zodat er daar geen problemen worden verwacht.\n\n", + "en": "The ground may become slippery. This situation is dangerous for pedestrians and for cyclists. Road traffic is slowed down and can even become dangerous. Be careful.", + "de": "Der Boden kann glatt werden. Dies ist für Fußgänger oder Radfahrer gefährlich. Der Verkehr kann verlangsamen und gefährlich werden. Seien Sie vorsichtig." + }, + "fromTimestamp": "2024-01-12T07:00:00+01:00", + "toTimestamp": "2024-01-12T13:00:00+01:00" + } + ] + }, + "module": [ + { + "type": "svg", + "data": { + "url": { + "nl": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=pollen&l=nl&k=4a343a758cad373cfd3cb0f34f47e3e4", + "fr": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=pollen&l=fr&k=4a343a758cad373cfd3cb0f34f47e3e4", + "en": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=pollen&l=en&k=4a343a758cad373cfd3cb0f34f47e3e4", + "de": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&ins=92045&e=pollen&l=de&k=4a343a758cad373cfd3cb0f34f47e3e4" + }, + "ratio": 3.0458333333333334 + } + }, + { + "type": "uv", + "data": { + "levelValue": 0.7, + "level": { + "nl": "Laag", + "fr": "Faible", + "en": "Low", + "de": "Niedrig" + }, + "title": { + "nl": "Uv-index", + "fr": "Indice UV", + "en": "UV Index", + "de": "UV Index" + } + } + }, + { + "type": "observation", + "data": { + "count": 340, + "title": { + "nl": "Waarnemingen vandaag", + "fr": "Observations d'aujourd'hui", + "en": "Today's Observations", + "de": "Beobachtungen heute" + } + } + }, + { + "type": "svg", + "data": { + "url": { + "nl": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&e=efem&l=nl&k=4a343a758cad373cfd3cb0f34f47e3e4", + "fr": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&e=efem&l=fr&k=4a343a758cad373cfd3cb0f34f47e3e4", + "en": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&e=efem&l=en&k=4a343a758cad373cfd3cb0f34f47e3e4", + "de": "https:\/\/app.meteo.be\/services\/appv4\/?s=getSvg&e=efem&l=de&k=4a343a758cad373cfd3cb0f34f47e3e4" + }, + "ratio": 1.6587926509186353 + } + } + ], + "animation": { + "localisationLayer": "https:\/\/app.meteo.be\/services\/appv4\/?s=getLocalizationLayer&lat=50.4&long=4.8&f=2&k=e5e36cd15199b890c31d2ce1de85b79d", + "localisationLayerRatioX": 0.6551, + "localisationLayerRatioY": 0.5435, + "speed": 0.3, + "type": "10min", + "unit": { + "fr": "mm\/10min", + "nl": "mm\/10min", + "en": "mm\/10min", + "de": "mm\/10min" + }, + "country": "BE", + "sequence": [ + { + "time": "2024-01-12T09:00:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120810&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T09:10:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120820&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T09:20:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120830&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T09:30:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120840&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T09:40:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120850&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T09:50:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120900&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:00:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120910&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:10:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120920&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:20:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120930&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:30:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120940&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:40:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401120950&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T10:50:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121000&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:00:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121010&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:10:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121020&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:20:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121030&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:30:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121040&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:40:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121050&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T11:50:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121100&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:00:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121110&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:10:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121120&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:20:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121130&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:30:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121140&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:40:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121150&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T12:50:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121200&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:00:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121210&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:10:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121220&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:20:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121230&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:30:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121240&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:40:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121250&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + }, + { + "time": "2024-01-12T13:50:00+01:00", + "uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202401121300&f=2&k=2160a92594985471351907ee5cc75d1f&d=202401120900", + "value": 0, + "position": 0, + "positionLower": 0, + "positionHigher": 0 + } + ], + "threshold": [], + "sequenceHint": { + "nl": "Geen regen voorzien op korte termijn", + "fr": "Pas de pluie prévue prochainement", + "en": "No rain forecasted shortly", + "de": "Kein Regen erwartet in naher Zukunft" + } + }, + "todayObsCount": 340 +} \ No newline at end of file diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py new file mode 100644 index 0000000..276c82a --- /dev/null +++ b/tests/test_binary_sensor.py @@ -0,0 +1,27 @@ +from datetime import datetime + +from freezegun import freeze_time +from homeassistant.core import HomeAssistant +from pytest_homeassistant_custom_component.common import MockConfigEntry + +from custom_components.irm_kmi import IrmKmiCoordinator +from custom_components.irm_kmi.binary_sensor import IrmKmiWarning +from tests.conftest import get_api_data + + +@freeze_time(datetime.fromisoformat('2024-01-12T07:55:00+01:00')) +async def test_warning_data( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("be_forecast_warning.json") + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + + result = coordinator.warnings_from_data(api_data.get('for', {}).get('warning')) + + coordinator.data = {'warnings': result} + warning = IrmKmiWarning(coordinator, mock_config_entry) + warning.hass = hass + + assert warning.is_on + assert len(warning.extra_state_attributes['warnings']) == 2 diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 56bb67a..ca57350 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -35,7 +35,6 @@ async def test_full_user_flow( CONF_STYLE: OPTION_STYLE_STD, CONF_DARK_MODE: False}, ) - print(result2) assert result2.get("type") == FlowResultType.CREATE_ENTRY assert result2.get("title") == "test home" assert result2.get("data") == {CONF_ZONE: ENTITY_ID_HOME, diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index fa1e778..76374da 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1,4 +1,3 @@ -import json from datetime import datetime, timedelta from freezegun import freeze_time @@ -6,15 +5,11 @@ from homeassistant.components.weather import (ATTR_CONDITION_CLOUDY, ATTR_CONDITION_PARTLYCLOUDY, ATTR_CONDITION_RAINY, Forecast) from homeassistant.core import HomeAssistant -from pytest_homeassistant_custom_component.common import (MockConfigEntry, - load_fixture) +from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.irm_kmi.coordinator import IrmKmiCoordinator from custom_components.irm_kmi.data import CurrentWeatherData, IrmKmiForecast - - -def get_api_data(fixture: str) -> dict: - return json.loads(load_fixture(fixture)) +from tests.conftest import get_api_data async def test_jules_forgot_to_revert_update_interval_before_pushing( @@ -26,6 +21,30 @@ async def test_jules_forgot_to_revert_update_interval_before_pushing( assert timedelta(minutes=5) <= coordinator.update_interval +@freeze_time(datetime.fromisoformat('2024-01-12T07:10:00')) +async def test_warning_data( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("be_forecast_warning.json") + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + + result = coordinator.warnings_from_data(api_data.get('for', {}).get('warning')) + + assert isinstance(result, list) + assert len(result) == 2 + + first = result[0] + + assert first.get('starts_at').replace(tzinfo=None) < datetime.now() + assert first.get('ends_at').replace(tzinfo=None) > datetime.now() + + assert first.get('slug') == 'fog' + assert first.get('friendly_name') == 'Fog' + assert first.get('id') == 7 + assert first.get('level') == 1 + + @freeze_time(datetime.fromisoformat('2023-12-26T18:30:00')) def test_current_weather_be() -> None: api_data = get_api_data("forecast.json") From 1c4fa60612b08daba8ea1d74c35a2fe5300726c6 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 22:00:05 +0100 Subject: [PATCH 3/6] Fix test requirements --- requirements_tests.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_tests.txt b/requirements_tests.txt index 272fe62..f699628 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -1,6 +1,6 @@ -homeassistant==2024.1.2 +homeassistant==2024.1.3 pytest -pytest_homeassistant_custom_component @ git+https://github.com/MatthewFlamm/pytest-homeassistant-custom-component +pytest_homeassistant_custom_component==0.13.89 freezegun Pillow==10.1.0 isort \ No newline at end of file From dceb98ae1236f1733b6b818f72894fba9708eba4 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 22:22:39 +0100 Subject: [PATCH 4/6] Add TODO comment --- custom_components/irm_kmi/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/irm_kmi/data.py b/custom_components/irm_kmi/data.py index 606898f..3b5cce9 100644 --- a/custom_components/irm_kmi/data.py +++ b/custom_components/irm_kmi/data.py @@ -9,6 +9,7 @@ class IrmKmiForecast(Forecast): """Forecast class with additional attributes for IRM KMI""" # TODO: add condition_2 as well and evolution to match data from the API? + # TODO: remove the _fr and _nl to have only on 'text' attribute text_fr: str | None text_nl: str | None From c2ca26d975622306dc272723ed808ded36b92518 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 22:23:29 +0100 Subject: [PATCH 5/6] Fix typo --- custom_components/irm_kmi/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/irm_kmi/data.py b/custom_components/irm_kmi/data.py index 3b5cce9..4a045b1 100644 --- a/custom_components/irm_kmi/data.py +++ b/custom_components/irm_kmi/data.py @@ -9,7 +9,7 @@ class IrmKmiForecast(Forecast): """Forecast class with additional attributes for IRM KMI""" # TODO: add condition_2 as well and evolution to match data from the API? - # TODO: remove the _fr and _nl to have only on 'text' attribute + # TODO: remove the _fr and _nl to have only one 'text' attribute text_fr: str | None text_nl: str | None From 102a939679ce1eb17550c56998685c9bcc2821c2 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 23:03:01 +0100 Subject: [PATCH 6/6] Update README --- README.md | 41 +++++++++++++++++++++++++++++++++++++---- img/forecast.png | Bin 0 -> 42146 bytes img/sensors.png | Bin 0 -> 14494 bytes 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 img/forecast.png create mode 100644 img/sensors.png diff --git a/README.md b/README.md index ef6be10..113612b 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ The data is collected via their non-public mobile application API. Although the provider is Belgian, the data is available for Belgium 🇧🇪, Luxembourg 🇱🇺, and The Netherlands 🇳🇱 -**Note: this is still under development, new versions may not be backward compatible.** - ## Installing via HACS 1. Go to HACS > Integrations @@ -23,6 +21,7 @@ This integration provides the following things: - A weather entity with current weather conditions - Weather forecasts (hourly, daily and twice-daily) [using the service `weather.get_forecasts`](https://www.home-assistant.io/integrations/weather/#service-weatherget_forecasts) - A camera entity for rain radar and short-term rain previsions +- A binary sensor for weather warnings The following options are available: @@ -33,6 +32,8 @@ The following options are available:
Show screenshots +
+


@@ -45,8 +46,7 @@ weather condition is taken into account in this integration.
Example of two weather conditions 2. The trends for 14 days are not shown -3. The warnings shown in the app are not shown by the integration -4. The provider only has data for Belgium, Luxembourg and The Netherlands +3. The provider only has data for Belgium, Luxembourg and The Netherlands ## Mapping between IRM KMI and Home Assistant weather conditions @@ -71,6 +71,39 @@ Mapping was established based on my own interpretation of the icons and conditio | windy-variant | Wind and clouds | | | +## Warning details + +The warning binary sensor is on if a warning is currently relevant (i.e. warning start time < current time < warning end time). +Warnings may be issued by the IRM KMI ahead of time but the binary sensor is only on when at least one of the issued warnings is relevant. + +The binary sensor has an additional attribute called `warnings`, with a list of warnings for the current location. +Warnings in the list may be warning issued ahead of time. + +Each element in the list has the following attributes: + * `slug: str`: warning slug type, can be used for automation and does not change with language setting. Example: `ice_or_snow` + * `id: int`: internal id for the warning type used by the IRM KMI api. + * `level: int`: warning severity, from 1 (lower risk) to 3 (higher risk) + * `friendly_name: str`: language specific name for the warning type. Examples: `Ice or snow`, `Chute de neige ou verglas`, `Sneeuw of ijzel`, `Glätte` + * `text: str`: language specific additional information about the warning + * `starts_at: datetime`: time at which the warning starts being relevant + * `ends_at: datetime`: time at which the warning stops being relevant + +The following table summarizes the different known warning types. Other warning types may be returned and will have `unknown` as slug. Feel free to open an issue with the id and the English friendly name to have it added to this integration. + +| Warning slug | Warning id | Friendly name (en, fr, nl, de) | +|-----------------------------|------------|------------------------------------------------------------------------------------------| +| wind | 0 | Wind, Vent, Wind, Wind | +| rain | 1 | Rain, Pluie, Regen, Regen | +| ice_or_snow | 2 | Ice or snow, Chute de neige ou verglas, Sneeuw of ijzel, Glätte | +| thunder | 3 | Thunder, Orage, Onweer, Gewitter | +| fog | 7 | Fog, Brouillard, Mist, Nebel | +| cold | 9 | Cold, Froid, Koude, Kalt | +| thunder_wind_rain | 12 | Thunder Wind Rain, Orage, rafales et averses, Onweer Wind Regen, Gewitter Windböen Regen | +| thunderstorm_strong_gusts | 13 | Thunderstorm & strong gusts, Orage et rafales, Onweer en wind, Gewitter und Windböen | +| thunderstorm_large_rainfall | 14 | Thunderstorm & large rainfall, Orage et averses, Onweer en regen, Gewitter und Regen | +| storm_surge | 15 | Storm surge, Marée forte, Stormtij, Sturmflut | +| coldspell | 17 | Coldspell, Vague de froid, Koude, Koude | + ## Disclaimer This is a personal project and isn't in any way affiliated with, sponsored or endorsed by [The Royal Meteorological diff --git a/img/forecast.png b/img/forecast.png new file mode 100644 index 0000000000000000000000000000000000000000..2e51f655fae979471690699b23124896a2a4c886 GIT binary patch literal 42146 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 RaPJUJUPeW_P|`H;e*sgG;OhVY literal 0 HcmV?d00001