mirror of
https://github.com/jdejaegh/irm-kmi-ha.git
synced 2025-06-27 11:39:26 +02:00
commit
2aa41f0155
7 changed files with 35 additions and 18 deletions
|
@ -87,6 +87,7 @@ Each element in the list has the following attributes:
|
||||||
* `text: str`: language specific additional information about the warning
|
* `text: str`: language specific additional information about the warning
|
||||||
* `starts_at: datetime`: time at which the warning starts being relevant
|
* `starts_at: datetime`: time at which the warning starts being relevant
|
||||||
* `ends_at: datetime`: time at which the warning stops being relevant
|
* `ends_at: datetime`: time at which the warning stops being relevant
|
||||||
|
* `is_active: bool`: `true` if `starts_at` < now < `ends_at`
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
@ -104,6 +105,9 @@ The following table summarizes the different known warning types. Other warning
|
||||||
| storm_surge | 15 | Storm surge, Marée forte, Stormtij, Sturmflut |
|
| storm_surge | 15 | Storm surge, Marée forte, Stormtij, Sturmflut |
|
||||||
| coldspell | 17 | Coldspell, Vague de froid, Koude, Koude |
|
| coldspell | 17 | Coldspell, Vague de froid, Koude, Koude |
|
||||||
|
|
||||||
|
The sensor has an attribute called `active_warnings_friendly_names`, holding a comma separated list of the friendly names
|
||||||
|
of the currently active warnings (e.g. `Fog, Ice or snow`). There is no particular order for the list.
|
||||||
|
|
||||||
## 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
|
||||||
|
|
|
@ -59,4 +59,12 @@ class IrmKmiWarning(CoordinatorEntity, BinarySensorEntity):
|
||||||
def extra_state_attributes(self) -> dict:
|
def extra_state_attributes(self) -> dict:
|
||||||
"""Return the camera state attributes."""
|
"""Return the camera state attributes."""
|
||||||
attrs = {"warnings": self.coordinator.data.get('warnings', [])}
|
attrs = {"warnings": self.coordinator.data.get('warnings', [])}
|
||||||
|
|
||||||
|
now = datetime.datetime.now(tz=pytz.timezone(self.hass.config.time_zone))
|
||||||
|
for warning in attrs['warnings']:
|
||||||
|
warning['is_active'] = warning.get('starts_at') < now < warning.get('ends_at')
|
||||||
|
|
||||||
|
attrs["active_warnings_friendly_names"] = ", ".join([warning['friendly_name'] for warning in attrs['warnings']
|
||||||
|
if warning['is_active']])
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
|
@ -71,7 +71,6 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
raise UpdateFailed(f"Error communicating with API: {err}")
|
raise UpdateFailed(f"Error communicating with API: {err}")
|
||||||
|
|
||||||
if api_data.get('cityName', None) in OUT_OF_BENELUX:
|
if api_data.get('cityName', None) in OUT_OF_BENELUX:
|
||||||
# TODO create a repair when this triggers
|
|
||||||
_LOGGER.info(f"Config state: {self._config_entry.state}")
|
_LOGGER.info(f"Config state: {self._config_entry.state}")
|
||||||
_LOGGER.error(f"The zone {self._zone} is now out of Benelux and forecast is only available in Benelux."
|
_LOGGER.error(f"The zone {self._zone} is now out of Benelux and forecast is only available in Benelux."
|
||||||
f"Associated device is now disabled. Move the zone back in Benelux and re-enable to fix "
|
f"Associated device is now disabled. Move the zone back in Benelux and re-enable to fix "
|
||||||
|
@ -130,7 +129,7 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
"""From the API data, create the object that will be used in the entities"""
|
"""From the API data, create the object that will be used in the entities"""
|
||||||
return ProcessedCoordinatorData(
|
return ProcessedCoordinatorData(
|
||||||
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=self.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'))
|
warnings=self.warnings_from_data(api_data.get('for', {}).get('warning'))
|
||||||
|
@ -257,8 +256,7 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
|
|
||||||
return forecasts
|
return forecasts
|
||||||
|
|
||||||
@staticmethod
|
def daily_list_to_forecast(self, data: List[dict] | None) -> List[Forecast] | None:
|
||||||
def daily_list_to_forecast(data: List[dict] | None) -> List[Forecast] | None:
|
|
||||||
"""Parse data from the API to create a list of daily forecasts"""
|
"""Parse data from the API to create a list of daily forecasts"""
|
||||||
if data is None or not isinstance(data, list) or len(data) == 0:
|
if data is None or not isinstance(data, list) or len(data) == 0:
|
||||||
return None
|
return None
|
||||||
|
@ -295,8 +293,7 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
precipitation_probability=f.get('precipChance', None),
|
precipitation_probability=f.get('precipChance', None),
|
||||||
wind_bearing=f.get('wind', {}).get('dirText', {}).get('en'),
|
wind_bearing=f.get('wind', {}).get('dirText', {}).get('en'),
|
||||||
is_daytime=is_daytime,
|
is_daytime=is_daytime,
|
||||||
text_fr=f.get('text', {}).get('fr'),
|
text=f.get('text', {}).get(self.hass.config.language, ""),
|
||||||
text_nl=f.get('text', {}).get('nl')
|
|
||||||
)
|
)
|
||||||
forecasts.append(forecast)
|
forecasts.append(forecast)
|
||||||
if is_daytime or idx == 0:
|
if is_daytime or idx == 0:
|
||||||
|
@ -347,10 +344,10 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
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:
|
def warnings_from_data(self, warning_data: list | None) -> List[WarningData]:
|
||||||
"""Create a list of warning data instances based on the api data"""
|
"""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:
|
if warning_data is None or not isinstance(warning_data, list) or len(warning_data) == 0:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
result = list()
|
result = list()
|
||||||
for data in warning_data:
|
for data in warning_data:
|
||||||
|
@ -379,4 +376,4 @@ class IrmKmiCoordinator(DataUpdateCoordinator):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return result if len(result) > 0 else None
|
return result if len(result) > 0 else []
|
||||||
|
|
|
@ -9,9 +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: str | None
|
||||||
text_fr: str | None
|
|
||||||
text_nl: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class CurrentWeatherData(TypedDict, total=False):
|
class CurrentWeatherData(TypedDict, total=False):
|
||||||
|
@ -63,4 +61,4 @@ class ProcessedCoordinatorData(TypedDict, total=False):
|
||||||
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
|
warnings: List[WarningData]
|
||||||
|
|
3
tests/fixtures/forecast.json
vendored
3
tests/fixtures/forecast.json
vendored
|
@ -66,7 +66,8 @@
|
||||||
"dayNight": "d",
|
"dayNight": "d",
|
||||||
"text": {
|
"text": {
|
||||||
"nl": "Foo",
|
"nl": "Foo",
|
||||||
"fr": "Bar"
|
"fr": "Bar",
|
||||||
|
"en": "Hey!"
|
||||||
},
|
},
|
||||||
"dawnRiseSeconds": "31440",
|
"dawnRiseSeconds": "31440",
|
||||||
"dawnSetSeconds": "60180",
|
"dawnSetSeconds": "60180",
|
||||||
|
|
|
@ -25,3 +25,8 @@ async def test_warning_data(
|
||||||
|
|
||||||
assert warning.is_on
|
assert warning.is_on
|
||||||
assert len(warning.extra_state_attributes['warnings']) == 2
|
assert len(warning.extra_state_attributes['warnings']) == 2
|
||||||
|
|
||||||
|
for w in warning.extra_state_attributes['warnings']:
|
||||||
|
assert w['is_active']
|
||||||
|
|
||||||
|
assert warning.extra_state_attributes['active_warnings_friendly_names'] == "Fog, Ice or snow"
|
||||||
|
|
|
@ -82,9 +82,14 @@ def test_current_weather_nl() -> None:
|
||||||
|
|
||||||
|
|
||||||
@freeze_time(datetime.fromisoformat('2023-12-26T18:30:00.028724'))
|
@freeze_time(datetime.fromisoformat('2023-12-26T18:30:00.028724'))
|
||||||
def test_daily_forecast() -> None:
|
async def test_daily_forecast(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry
|
||||||
|
) -> None:
|
||||||
api_data = get_api_data("forecast.json").get('for', {}).get('daily')
|
api_data = get_api_data("forecast.json").get('for', {}).get('daily')
|
||||||
result = IrmKmiCoordinator.daily_list_to_forecast(api_data)
|
|
||||||
|
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
|
||||||
|
result = coordinator.daily_list_to_forecast(api_data)
|
||||||
|
|
||||||
assert isinstance(result, list)
|
assert isinstance(result, list)
|
||||||
assert len(result) == 8
|
assert len(result) == 8
|
||||||
|
@ -100,8 +105,7 @@ def test_daily_forecast() -> None:
|
||||||
precipitation_probability=0,
|
precipitation_probability=0,
|
||||||
wind_bearing='S',
|
wind_bearing='S',
|
||||||
is_daytime=True,
|
is_daytime=True,
|
||||||
text_fr='Bar',
|
text='Hey!',
|
||||||
text_nl='Foo'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result[1] == expected
|
assert result[1] == expected
|
||||||
|
|
Loading…
Add table
Reference in a new issue