mirror of
https://github.com/jdejaegh/irm-kmi-ha.git
synced 2025-06-27 03:35:56 +02:00
Create new service: irm_kmi.get_forecasts_radar
This commit is contained in:
parent
22b7305e14
commit
121b6e50c3
12 changed files with 178 additions and 13 deletions
|
@ -13,8 +13,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import issue_registry
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (TimestampDataUpdateCoordinator,
|
||||
UpdateFailed)
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
TimestampDataUpdateCoordinator, UpdateFailed)
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from .api import IrmKmiApiClient, IrmKmiApiError
|
||||
|
@ -168,6 +168,7 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
|
|||
current_weather=IrmKmiCoordinator.current_weather_from_data(api_data),
|
||||
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')),
|
||||
radar_forecast=IrmKmiCoordinator.radar_list_to_forecast(api_data.get('animation', {})),
|
||||
animation=await self._async_animation_data(api_data=api_data),
|
||||
warnings=self.warnings_from_data(api_data.get('for', {}).get('warning')),
|
||||
pollen=await self._async_pollen_data(api_data=api_data)
|
||||
|
@ -317,6 +318,21 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
|
|||
|
||||
return forecasts
|
||||
|
||||
@staticmethod
|
||||
def radar_list_to_forecast(data: dict | None) -> List[Forecast] | None:
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
forecast = list()
|
||||
for f in data.get("sequence", []):
|
||||
forecast.append(
|
||||
Forecast(
|
||||
datetime=f.get("time"),
|
||||
native_precipitation=f.get('value')
|
||||
)
|
||||
)
|
||||
return forecast
|
||||
|
||||
def daily_list_to_forecast(self, data: List[dict] | None) -> List[Forecast] | None:
|
||||
"""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:
|
||||
|
|
|
@ -60,6 +60,7 @@ class ProcessedCoordinatorData(TypedDict, total=False):
|
|||
current_weather: CurrentWeatherData
|
||||
hourly_forecast: List[Forecast] | None
|
||||
daily_forecast: List[IrmKmiForecast] | None
|
||||
radar_forecast: List[Forecast] | None
|
||||
animation: RadarAnimationData
|
||||
warnings: List[WarningData]
|
||||
pollen: dict
|
||||
|
|
5
custom_components/irm_kmi/icons.json
Normal file
5
custom_components/irm_kmi/icons.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"services": {
|
||||
"get_forecasts_radar": "mdi:weather-cloudy-clock"
|
||||
}
|
||||
}
|
11
custom_components/irm_kmi/services.yaml
Normal file
11
custom_components/irm_kmi/services.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
get_forecasts_radar:
|
||||
target:
|
||||
entity:
|
||||
integration: irm_kmi
|
||||
domain: weather
|
||||
fields:
|
||||
include_past_forecasts:
|
||||
required: true
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
|
@ -166,5 +166,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_forecasts_radar": {
|
||||
"name": "Get forecast from the radar",
|
||||
"description": "Get weather forecast from the radar. Only precipitation is available.",
|
||||
"fields": {
|
||||
"include_past_forecasts": {
|
||||
"name": "Include past forecasts",
|
||||
"description": "Also return forecasts for that are in the past."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,5 +166,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_forecasts_radar": {
|
||||
"name": "Obtenir les prévisions du radar",
|
||||
"description": "Obtenez les prévisions météorologiques depuis le radar. Seules les précipitations sont disponibles.",
|
||||
"fields": {
|
||||
"include_past_forecasts": {
|
||||
"name": "Inclure les prévisions passées",
|
||||
"description": "Retourne également les prévisions qui sont dans le passé."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,5 +166,17 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"get_forecasts_radar": {
|
||||
"name": "Get forecast from the radar",
|
||||
"description": "Weersverwachting van radar ophalen. Alleen neerslag is beschikbaar.",
|
||||
"fields": {
|
||||
"include_past_forecasts": {
|
||||
"name": "Verleden weersvoorspellingen opnemen",
|
||||
"description": "Geeft ook weersvoorspellingen uit het verleden."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
"""Support for IRM KMI weather."""
|
||||
import copy
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.components.weather import (Forecast, WeatherEntity,
|
||||
WeatherEntityFeature)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (UnitOfPrecipitationDepth, UnitOfPressure,
|
||||
UnitOfSpeed, UnitOfTemperature)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, SupportsResponse
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import CONF_USE_DEPRECATED_FORECAST, DOMAIN
|
||||
from .const import (OPTION_DEPRECATED_FORECAST_DAILY,
|
||||
|
@ -25,11 +29,25 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback):
|
||||
"""Set up the weather entry."""
|
||||
add_services()
|
||||
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
async_add_entities([IrmKmiWeather(coordinator, entry)])
|
||||
|
||||
|
||||
def add_services() -> None:
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
"get_forecasts_radar",
|
||||
cv.make_entity_service_schema({
|
||||
vol.Optional("include_past_forecasts"): vol.Boolean()
|
||||
}),
|
||||
IrmKmiWeather.get_forecasts_radar_service.__name__,
|
||||
supports_response=SupportsResponse.ONLY
|
||||
)
|
||||
|
||||
|
||||
class IrmKmiWeather(CoordinatorEntity, WeatherEntity):
|
||||
|
||||
def __init__(self,
|
||||
|
@ -41,7 +59,6 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity):
|
|||
self._name = entry.title
|
||||
self._attr_unique_id = entry.entry_id
|
||||
self._attr_device_info = coordinator.shared_device_info
|
||||
|
||||
self._deprecated_forecast_as = get_config_value(entry, CONF_USE_DEPRECATED_FORECAST)
|
||||
|
||||
if self._deprecated_forecast_as != OPTION_DEPRECATED_FORECAST_NOT_USED:
|
||||
|
@ -134,6 +151,20 @@ class IrmKmiWeather(CoordinatorEntity, WeatherEntity):
|
|||
|
||||
return [f for f in data if f.get('is_daytime')]
|
||||
|
||||
def get_forecasts_radar_service(self, include_past_forecasts: bool) -> List[Forecast] | None:
|
||||
"""
|
||||
Forecast service based on data from the radar. Only contains datetime and precipitation amount.
|
||||
The result always include the current 10 minutes interval, even if include_past_forecast is false.
|
||||
:param include_past_forecasts: whether to include data points that are in the past
|
||||
:return: ordered list of forecasts
|
||||
"""
|
||||
# now = datetime.now(tz=pytz.timezone(self.hass.config.time_zone))
|
||||
now = dt.now()
|
||||
now = now.replace(minute=(now.minute // 10) * 10, second=0, microsecond=0)
|
||||
|
||||
return [f for f in self.coordinator.data.get('radar_forecast')
|
||||
if include_past_forecasts or datetime.fromisoformat(f.get('datetime')) >= now]
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict:
|
||||
"""Here to keep the DEPRECATED forecast attribute.
|
||||
|
|
|
@ -14,7 +14,8 @@ from custom_components.irm_kmi.api import (IrmKmiApiError,
|
|||
IrmKmiApiParametersError)
|
||||
from custom_components.irm_kmi.const import (
|
||||
CONF_DARK_MODE, CONF_STYLE, CONF_USE_DEPRECATED_FORECAST, DOMAIN,
|
||||
OPTION_DEPRECATED_FORECAST_NOT_USED, OPTION_STYLE_STD, OPTION_DEPRECATED_FORECAST_TWICE_DAILY)
|
||||
OPTION_DEPRECATED_FORECAST_NOT_USED,
|
||||
OPTION_DEPRECATED_FORECAST_TWICE_DAILY, OPTION_STYLE_STD)
|
||||
|
||||
|
||||
def get_api_data(fixture: str) -> dict:
|
||||
|
|
10
tests/fixtures/forecast.json
vendored
10
tests/fixtures/forecast.json
vendored
|
@ -1408,7 +1408,7 @@
|
|||
{
|
||||
"time": "2023-12-26T17:40:00+01:00",
|
||||
"uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202312261650&f=2&k=4a71be18d6cb09f98c49c53f59902f8c&d=202312261720",
|
||||
"value": 0,
|
||||
"value": 0.1,
|
||||
"position": 0,
|
||||
"positionLower": 0,
|
||||
"positionHigher": 0
|
||||
|
@ -1416,7 +1416,7 @@
|
|||
{
|
||||
"time": "2023-12-26T17:50:00+01:00",
|
||||
"uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202312261700&f=2&k=4a71be18d6cb09f98c49c53f59902f8c&d=202312261720",
|
||||
"value": 0,
|
||||
"value": 0.01,
|
||||
"position": 0,
|
||||
"positionLower": 0,
|
||||
"positionHigher": 0
|
||||
|
@ -1424,7 +1424,7 @@
|
|||
{
|
||||
"time": "2023-12-26T18:00:00+01:00",
|
||||
"uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202312261710&f=2&k=4a71be18d6cb09f98c49c53f59902f8c&d=202312261720",
|
||||
"value": 0,
|
||||
"value": 0.12,
|
||||
"position": 0,
|
||||
"positionLower": 0,
|
||||
"positionHigher": 0
|
||||
|
@ -1432,7 +1432,7 @@
|
|||
{
|
||||
"time": "2023-12-26T18:10:00+01:00",
|
||||
"uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202312261720&f=2&k=4a71be18d6cb09f98c49c53f59902f8c&d=202312261720",
|
||||
"value": 0,
|
||||
"value": 1.2,
|
||||
"position": 0,
|
||||
"positionLower": 0,
|
||||
"positionHigher": 0
|
||||
|
@ -1440,7 +1440,7 @@
|
|||
{
|
||||
"time": "2023-12-26T18:20:00+01:00",
|
||||
"uri": "https:\/\/app.meteo.be\/services\/appv4\/?s=getIncaImage&i=202312261730&f=2&k=4a71be18d6cb09f98c49c53f59902f8c&d=202312261720",
|
||||
"value": 0,
|
||||
"value": 2,
|
||||
"position": 0,
|
||||
"positionLower": 0,
|
||||
"positionHigher": 0
|
||||
|
|
|
@ -8,8 +8,9 @@ from homeassistant.core import HomeAssistant
|
|||
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, ProcessedCoordinatorData, \
|
||||
RadarAnimationData
|
||||
from custom_components.irm_kmi.data import (CurrentWeatherData, IrmKmiForecast,
|
||||
ProcessedCoordinatorData,
|
||||
RadarAnimationData)
|
||||
from custom_components.irm_kmi.pollen import PollenParser
|
||||
from tests.conftest import get_api_data
|
||||
|
||||
|
@ -177,3 +178,24 @@ async def test_refresh_succeed_even_when_pollen_and_radar_fail(
|
|||
assert result.get('animation').get('hint') == "This will remain unchanged"
|
||||
|
||||
assert result.get('pollen') == {'foo': 'bar'}
|
||||
|
||||
|
||||
def test_radar_forecast() -> None:
|
||||
api_data = get_api_data("forecast.json")
|
||||
result = IrmKmiCoordinator.radar_list_to_forecast(api_data.get('animation'))
|
||||
|
||||
expected = [
|
||||
Forecast(datetime="2023-12-26T17:00:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:10:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:20:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:30:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:40:00+01:00", native_precipitation=0.1),
|
||||
Forecast(datetime="2023-12-26T17:50:00+01:00", native_precipitation=0.01),
|
||||
Forecast(datetime="2023-12-26T18:00:00+01:00", native_precipitation=0.12),
|
||||
Forecast(datetime="2023-12-26T18:10:00+01:00", native_precipitation=1.2),
|
||||
Forecast(datetime="2023-12-26T18:20:00+01:00", native_precipitation=2),
|
||||
Forecast(datetime="2023-12-26T18:30:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T18:40:00+01:00", native_precipitation=0)
|
||||
]
|
||||
|
||||
assert expected == result
|
||||
|
|
|
@ -9,6 +9,8 @@ from homeassistant.core import HomeAssistant
|
|||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
from custom_components.irm_kmi import IrmKmiCoordinator, IrmKmiWeather
|
||||
from custom_components.irm_kmi.data import ProcessedCoordinatorData
|
||||
from tests.conftest import get_api_data
|
||||
|
||||
|
||||
@freeze_time(datetime.fromisoformat("2023-12-28T15:30:00+01:00"))
|
||||
|
@ -91,3 +93,43 @@ async def test_forecast_attribute_same_as_service_call(
|
|||
result_forecast: List[Forecast] = weather.extra_state_attributes['forecast']
|
||||
|
||||
assert result_service == result_forecast
|
||||
|
||||
|
||||
@freeze_time(datetime.fromisoformat("2023-12-26T17:58:03+01:00"))
|
||||
async def test_radar_forecast_service(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry
|
||||
):
|
||||
hass.config.time_zone = 'Europe/Brussels'
|
||||
coordinator = IrmKmiCoordinator(hass, mock_config_entry)
|
||||
|
||||
api_data = get_api_data("forecast.json")
|
||||
data = IrmKmiCoordinator.radar_list_to_forecast(api_data.get('animation'))
|
||||
|
||||
coordinator.data = ProcessedCoordinatorData(
|
||||
radar_forecast=data
|
||||
)
|
||||
|
||||
weather = IrmKmiWeather(coordinator, mock_config_entry)
|
||||
|
||||
result_service: List[Forecast] = weather.get_forecasts_radar_service(False)
|
||||
|
||||
expected = [
|
||||
Forecast(datetime="2023-12-26T17:00:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:10:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:20:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:30:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T17:40:00+01:00", native_precipitation=0.1),
|
||||
Forecast(datetime="2023-12-26T17:50:00+01:00", native_precipitation=0.01),
|
||||
Forecast(datetime="2023-12-26T18:00:00+01:00", native_precipitation=0.12),
|
||||
Forecast(datetime="2023-12-26T18:10:00+01:00", native_precipitation=1.2),
|
||||
Forecast(datetime="2023-12-26T18:20:00+01:00", native_precipitation=2),
|
||||
Forecast(datetime="2023-12-26T18:30:00+01:00", native_precipitation=0),
|
||||
Forecast(datetime="2023-12-26T18:40:00+01:00", native_precipitation=0)
|
||||
]
|
||||
|
||||
assert result_service == expected[5:]
|
||||
|
||||
result_service: List[Forecast] = weather.get_forecasts_radar_service(True)
|
||||
|
||||
assert result_service == expected
|
||||
|
|
Loading…
Add table
Reference in a new issue