From bd26a99b0c8c456017c018f5cac3c2112e42e58c Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sat, 13 Jan 2024 21:54:44 +0100 Subject: [PATCH] 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")