mirror of
https://github.com/jdejaegh/irm-kmi-ha.git
synced 2025-06-27 03:35:56 +02:00
Merge pull request #4 from jdejaegh/warnings
Add binary sensor for weather warnings
This commit is contained in:
commit
b6e445f9fd
13 changed files with 1893 additions and 19 deletions
41
README.md
41
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 🇳🇱
|
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
|
## Installing via HACS
|
||||||
|
|
||||||
1. Go to HACS > Integrations
|
1. Go to HACS > Integrations
|
||||||
|
@ -23,6 +21,7 @@ This integration provides the following things:
|
||||||
- A weather entity with current weather conditions
|
- 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)
|
- 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 camera entity for rain radar and short-term rain previsions
|
||||||
|
- A binary sensor for weather warnings
|
||||||
|
|
||||||
The following options are available:
|
The following options are available:
|
||||||
|
|
||||||
|
@ -33,6 +32,8 @@ The following options are available:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Show screenshots</summary>
|
<summary>Show screenshots</summary>
|
||||||
|
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/sensors.png"/> <br>
|
||||||
|
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/forecast.png"/> <br>
|
||||||
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_light.png"/> <br>
|
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_light.png"/> <br>
|
||||||
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_dark.png"/> <br>
|
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_dark.png"/> <br>
|
||||||
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_sat.png"/>
|
<img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/camera_sat.png"/>
|
||||||
|
@ -45,8 +46,7 @@ weather condition is taken into account in this integration.
|
||||||
<br><img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/monday.png" height="150" alt="Example of two weather conditions">
|
<br><img src="https://github.com/jdejaegh/irm-kmi-ha/raw/main/img/monday.png" height="150" alt="Example of two weather conditions">
|
||||||
|
|
||||||
2. The trends for 14 days are not shown
|
2. The trends for 14 days are not shown
|
||||||
3. The warnings shown in the app are not shown by the integration
|
3. The provider only has data for Belgium, Luxembourg and The Netherlands
|
||||||
4. The provider only has data for Belgium, Luxembourg and The Netherlands
|
|
||||||
|
|
||||||
## Mapping between IRM KMI and Home Assistant weather conditions
|
## 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 | | |
|
| 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
|
## Disclaimer
|
||||||
|
|
||||||
This is a personal project and isn't in any way affiliated with, sponsored or endorsed by [The Royal Meteorological
|
This is a personal project and isn't in any way affiliated with, sponsored or endorsed by [The Royal Meteorological
|
||||||
|
|
62
custom_components/irm_kmi/binary_sensor.py
Normal file
62
custom_components/irm_kmi/binary_sensor.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
"""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 (BinarySensorDeviceClass,
|
||||||
|
BinarySensorEntity)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
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 DOMAIN, IrmKmiCoordinator
|
||||||
|
|
||||||
|
_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:
|
||||||
|
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:
|
||||||
|
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
|
|
@ -15,7 +15,7 @@ from homeassistant.components.weather import (ATTR_CONDITION_CLEAR_NIGHT,
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
DOMAIN: Final = 'irm_kmi'
|
DOMAIN: Final = 'irm_kmi'
|
||||||
PLATFORMS: Final = [Platform.WEATHER, Platform.CAMERA]
|
PLATFORMS: Final = [Platform.WEATHER, Platform.CAMERA, Platform.BINARY_SENSOR]
|
||||||
CONFIG_FLOW_VERSION = 3
|
CONFIG_FLOW_VERSION = 3
|
||||||
|
|
||||||
OUT_OF_BENELUX: Final = ["außerhalb der Benelux (Brussels)",
|
OUT_OF_BENELUX: Final = ["außerhalb der Benelux (Brussels)",
|
||||||
|
@ -121,3 +121,16 @@ IRM_KMI_TO_HA_CONDITION_MAP: Final = {
|
||||||
(27, 'd'): ATTR_CONDITION_EXCEPTIONAL,
|
(27, 'd'): ATTR_CONDITION_EXCEPTIONAL,
|
||||||
(27, 'n'): 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'}
|
||||||
|
|
|
@ -18,10 +18,11 @@ from homeassistant.helpers.update_coordinator import (DataUpdateCoordinator,
|
||||||
from .api import IrmKmiApiClient, IrmKmiApiError
|
from .api import IrmKmiApiClient, IrmKmiApiError
|
||||||
from .const import CONF_DARK_MODE, CONF_STYLE, DOMAIN
|
from .const import CONF_DARK_MODE, CONF_STYLE, DOMAIN
|
||||||
from .const import IRM_KMI_TO_HA_CONDITION_MAP as CDT_MAP
|
from .const import IRM_KMI_TO_HA_CONDITION_MAP as CDT_MAP
|
||||||
from .const import (LANGS, OPTION_STYLE_SATELLITE, OUT_OF_BENELUX,
|
from .const import LANGS
|
||||||
STYLE_TO_PARAM_MAP)
|
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,
|
from .data import (AnimationFrameData, CurrentWeatherData, IrmKmiForecast,
|
||||||
ProcessedCoordinatorData, RadarAnimationData)
|
ProcessedCoordinatorData, RadarAnimationData, WarningData)
|
||||||
from .rain_graph import RainGraph
|
from .rain_graph import RainGraph
|
||||||
from .utils import disable_from_config, get_config_value
|
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),
|
current_weather=IrmKmiCoordinator.current_weather_from_data(api_data),
|
||||||
daily_forecast=IrmKmiCoordinator.daily_list_to_forecast(api_data.get('for', {}).get('daily')),
|
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')),
|
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,
|
async def download_images_from_api(self,
|
||||||
|
@ -344,3 +346,37 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
return RainGraph(radar_animation, image_path, bg_size,
|
return RainGraph(radar_animation, image_path, bg_size,
|
||||||
dark_mode=self._dark_mode,
|
dark_mode=self._dark_mode,
|
||||||
tz=self.hass.config.time_zone)
|
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
|
||||||
|
|
|
@ -9,6 +9,7 @@ class IrmKmiForecast(Forecast):
|
||||||
"""Forecast class with additional attributes for IRM KMI"""
|
"""Forecast class with additional attributes for IRM KMI"""
|
||||||
|
|
||||||
# TODO: add condition_2 as well and evolution to match data from the API?
|
# TODO: add condition_2 as well and evolution to match data from the API?
|
||||||
|
# TODO: remove the _fr and _nl to have only one 'text' attribute
|
||||||
text_fr: str | None
|
text_fr: str | None
|
||||||
text_nl: str | None
|
text_nl: str | None
|
||||||
|
|
||||||
|
@ -45,9 +46,21 @@ class RadarAnimationData(TypedDict, total=False):
|
||||||
svg_animated: bytes | None
|
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):
|
class ProcessedCoordinatorData(TypedDict, total=False):
|
||||||
"""Data class that will be exposed to the entities consuming data from an IrmKmiCoordinator"""
|
"""Data class that will be exposed to the entities consuming data from an IrmKmiCoordinator"""
|
||||||
current_weather: CurrentWeatherData
|
current_weather: CurrentWeatherData
|
||||||
hourly_forecast: List[Forecast] | None
|
hourly_forecast: List[Forecast] | None
|
||||||
daily_forecast: List[IrmKmiForecast] | None
|
daily_forecast: List[IrmKmiForecast] | None
|
||||||
animation: RadarAnimationData
|
animation: RadarAnimationData
|
||||||
|
warnings: List[WarningData] | None
|
||||||
|
|
BIN
img/forecast.png
Normal file
BIN
img/forecast.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
BIN
img/sensors.png
Normal file
BIN
img/sensors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -1,6 +1,6 @@
|
||||||
homeassistant==2024.1.2
|
homeassistant==2024.1.3
|
||||||
pytest
|
pytest
|
||||||
pytest_homeassistant_custom_component @ git+https://github.com/MatthewFlamm/pytest-homeassistant-custom-component
|
pytest_homeassistant_custom_component==0.13.89
|
||||||
freezegun
|
freezegun
|
||||||
Pillow==10.1.0
|
Pillow==10.1.0
|
||||||
isort
|
isort
|
|
@ -17,6 +17,10 @@ from custom_components.irm_kmi.const import (
|
||||||
OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD)
|
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:
|
async def patched(url: str, params: dict | None = None) -> bytes:
|
||||||
if "cdn.knmi.nl" in url:
|
if "cdn.knmi.nl" in url:
|
||||||
file_name = "tests/fixtures/clouds_nl.png"
|
file_name = "tests/fixtures/clouds_nl.png"
|
||||||
|
|
1668
tests/fixtures/be_forecast_warning.json
vendored
Normal file
1668
tests/fixtures/be_forecast_warning.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
27
tests/test_binary_sensor.py
Normal file
27
tests/test_binary_sensor.py
Normal file
|
@ -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
|
|
@ -35,7 +35,6 @@ async def test_full_user_flow(
|
||||||
CONF_STYLE: OPTION_STYLE_STD,
|
CONF_STYLE: OPTION_STYLE_STD,
|
||||||
CONF_DARK_MODE: False},
|
CONF_DARK_MODE: False},
|
||||||
)
|
)
|
||||||
print(result2)
|
|
||||||
assert result2.get("type") == FlowResultType.CREATE_ENTRY
|
assert result2.get("type") == FlowResultType.CREATE_ENTRY
|
||||||
assert result2.get("title") == "test home"
|
assert result2.get("title") == "test home"
|
||||||
assert result2.get("data") == {CONF_ZONE: ENTITY_ID_HOME,
|
assert result2.get("data") == {CONF_ZONE: ENTITY_ID_HOME,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
@ -6,15 +5,11 @@ from homeassistant.components.weather import (ATTR_CONDITION_CLOUDY,
|
||||||
ATTR_CONDITION_PARTLYCLOUDY,
|
ATTR_CONDITION_PARTLYCLOUDY,
|
||||||
ATTR_CONDITION_RAINY, Forecast)
|
ATTR_CONDITION_RAINY, Forecast)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from pytest_homeassistant_custom_component.common import (MockConfigEntry,
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||||
load_fixture)
|
|
||||||
|
|
||||||
from custom_components.irm_kmi.coordinator import IrmKmiCoordinator
|
from custom_components.irm_kmi.coordinator import IrmKmiCoordinator
|
||||||
from custom_components.irm_kmi.data import CurrentWeatherData, IrmKmiForecast
|
from custom_components.irm_kmi.data import CurrentWeatherData, IrmKmiForecast
|
||||||
|
from tests.conftest import get_api_data
|
||||||
|
|
||||||
def get_api_data(fixture: str) -> dict:
|
|
||||||
return json.loads(load_fixture(fixture))
|
|
||||||
|
|
||||||
|
|
||||||
async def test_jules_forgot_to_revert_update_interval_before_pushing(
|
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
|
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'))
|
@freeze_time(datetime.fromisoformat('2023-12-26T18:30:00'))
|
||||||
def test_current_weather_be() -> None:
|
def test_current_weather_be() -> None:
|
||||||
api_data = get_api_data("forecast.json")
|
api_data = get_api_data("forecast.json")
|
||||||
|
|
Loading…
Add table
Reference in a new issue