From f1e7c267e6b91375c71011d66a33ce8934e66ed5 Mon Sep 17 00:00:00 2001 From: Jules Dejaeghere Date: Sun, 9 Jun 2024 20:37:14 +0200 Subject: [PATCH] Add next sunset sunrise sensors --- custom_components/irm_kmi/coordinator.py | 3 +- custom_components/irm_kmi/data.py | 1 + custom_components/irm_kmi/sensor.py | 38 +++++++++++++++++ .../irm_kmi/translations/en.json | 6 +++ .../irm_kmi/translations/fr.json | 6 +++ .../irm_kmi/translations/nl.json | 6 +++ ...est_warning_sensors.py => test_sensors.py} | 42 ++++++++++++++++++- 7 files changed, 100 insertions(+), 2 deletions(-) rename tests/{test_warning_sensors.py => test_sensors.py} (73%) diff --git a/custom_components/irm_kmi/coordinator.py b/custom_components/irm_kmi/coordinator.py index 22d14fc..b433c3a 100644 --- a/custom_components/irm_kmi/coordinator.py +++ b/custom_components/irm_kmi/coordinator.py @@ -176,7 +176,8 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator): 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) + pollen=await self._async_pollen_data(api_data=api_data), + country=api_data.get('country') ) async def download_images_from_api(self, diff --git a/custom_components/irm_kmi/data.py b/custom_components/irm_kmi/data.py index b64f59e..566155f 100644 --- a/custom_components/irm_kmi/data.py +++ b/custom_components/irm_kmi/data.py @@ -73,3 +73,4 @@ class ProcessedCoordinatorData(TypedDict, total=False): animation: RadarAnimationData warnings: List[WarningData] pollen: dict + country: str diff --git a/custom_components/irm_kmi/sensor.py b/custom_components/irm_kmi/sensor.py index 39c8ab9..577ea2a 100644 --- a/custom_components/irm_kmi/sensor.py +++ b/custom_components/irm_kmi/sensor.py @@ -12,6 +12,7 @@ from homeassistant.util import dt from custom_components.irm_kmi import DOMAIN, IrmKmiCoordinator from custom_components.irm_kmi.const import POLLEN_NAMES, POLLEN_TO_ICON_MAP +from custom_components.irm_kmi.data import IrmKmiForecast from custom_components.irm_kmi.pollen import PollenParser _LOGGER = logging.getLogger(__name__) @@ -23,6 +24,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e async_add_entities([IrmKmiPollen(coordinator, entry, pollen.lower()) for pollen in POLLEN_NAMES]) async_add_entities([IrmKmiNextWarning(coordinator, entry),]) + if coordinator.data.get('country') != 'NL': + async_add_entities([IrmKmiNextSunMove(coordinator, entry, move) for move in ['sunset', 'sunrise']]) + class IrmKmiPollen(CoordinatorEntity, SensorEntity): """Representation of a pollen sensor""" @@ -96,3 +100,37 @@ class IrmKmiNextWarning(CoordinatorEntity, SensorEntity): [warning['friendly_name'] for warning in attrs['next_warnings'] if warning['friendly_name'] != '']) return attrs + + +class IrmKmiNextSunMove(CoordinatorEntity, SensorEntity): + """Representation of the next sunrise or sunset""" + + _attr_has_entity_name = True + _attr_device_class = SensorDeviceClass.TIMESTAMP + _attr_attribution = "Weather data from the Royal Meteorological Institute of Belgium meteo.be" + + def __init__(self, + coordinator: IrmKmiCoordinator, + entry: ConfigEntry, + move: str) -> None: + assert move in ['sunset', 'sunrise'] + super().__init__(coordinator) + SensorEntity.__init__(self) + self._attr_unique_id = f"{entry.entry_id}-next-{move}" + self.entity_id = sensor.ENTITY_ID_FORMAT.format(f"{str(entry.title).lower()}_next_{move}") + self._attr_device_info = coordinator.shared_device_info + self._attr_translation_key = f"next_{move}" + self._move: str = move + self._attr_icon = 'mdi:weather-sunset-down' if move == 'sunset' else 'mdi:weather-sunset-up' + + @property + def native_value(self) -> datetime.datetime | None: + """Return the timestamp for the next sunrise or sunset""" + now = dt.now() + data: list[IrmKmiForecast] = self.coordinator.data.get('daily_forecast') + + upcoming = [f.get(self._move) for f in data if f.get(self._move) >= now] + + if len(upcoming) > 0: + return upcoming[0] + return None diff --git a/custom_components/irm_kmi/translations/en.json b/custom_components/irm_kmi/translations/en.json index 9f898b4..0a5a529 100644 --- a/custom_components/irm_kmi/translations/en.json +++ b/custom_components/irm_kmi/translations/en.json @@ -93,6 +93,12 @@ "next_warning": { "name": "Next warning" }, + "next_sunrise": { + "name": "Next sunrise" + }, + "next_sunset": { + "name": "Next sunset" + }, "pollen_alder": { "name": "Alder pollen", "state": { diff --git a/custom_components/irm_kmi/translations/fr.json b/custom_components/irm_kmi/translations/fr.json index 219a173..00f5280 100644 --- a/custom_components/irm_kmi/translations/fr.json +++ b/custom_components/irm_kmi/translations/fr.json @@ -93,6 +93,12 @@ "next_warning": { "name": "Prochain avertissement" }, + "next_sunrise": { + "name": "Prochain lever de soleil" + }, + "next_sunset": { + "name": "Prochain coucher de soleil" + }, "pollen_alder": { "name": "Pollen d'aulne", "state": { diff --git a/custom_components/irm_kmi/translations/nl.json b/custom_components/irm_kmi/translations/nl.json index 9c3a27c..1545359 100644 --- a/custom_components/irm_kmi/translations/nl.json +++ b/custom_components/irm_kmi/translations/nl.json @@ -93,6 +93,12 @@ "next_warning": { "name": "Volgende waarschuwing" }, + "next_sunrise": { + "name": "Volgende zonsopkomst" + }, + "next_sunset": { + "name": "Volgende zonsondergang" + }, "pollen_alder": { "name": "Elzenpollen", "state": { diff --git a/tests/test_warning_sensors.py b/tests/test_sensors.py similarity index 73% rename from tests/test_warning_sensors.py rename to tests/test_sensors.py index 259f7c5..88863fc 100644 --- a/tests/test_warning_sensors.py +++ b/tests/test_sensors.py @@ -7,7 +7,7 @@ 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 custom_components.irm_kmi.const import CONF_LANGUAGE_OVERRIDE -from custom_components.irm_kmi.sensor import IrmKmiNextWarning +from custom_components.irm_kmi.sensor import IrmKmiNextWarning, IrmKmiNextSunMove from tests.conftest import get_api_data @@ -126,3 +126,43 @@ async def test_next_warning_none_when_no_warnings( assert len(warning.extra_state_attributes['next_warnings']) == 0 assert warning.extra_state_attributes['next_warnings_friendly_names'] == "" + + +@freeze_time(datetime.fromisoformat('2023-12-26T18:30:00+01:00')) +async def test_next_sunrise_sunset( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("forecast.json") + + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + + result = await coordinator.daily_list_to_forecast(api_data.get('for', {}).get('daily')) + + coordinator.data = {'daily_forecast': result} + + sunset = IrmKmiNextSunMove(coordinator, mock_config_entry, 'sunset') + sunrise = IrmKmiNextSunMove(coordinator, mock_config_entry, 'sunrise') + + assert datetime.fromisoformat(sunrise.state) == datetime.fromisoformat('2023-12-27T08:44:00+01:00') + assert datetime.fromisoformat(sunset.state) == datetime.fromisoformat('2023-12-27T16:43:00+01:00') + + +@freeze_time(datetime.fromisoformat('2023-12-26T15:30:00+01:00')) +async def test_next_sunrise_sunset_bis( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry +) -> None: + api_data = get_api_data("forecast.json") + + coordinator = IrmKmiCoordinator(hass, mock_config_entry) + + result = await coordinator.daily_list_to_forecast(api_data.get('for', {}).get('daily')) + + coordinator.data = {'daily_forecast': result} + + sunset = IrmKmiNextSunMove(coordinator, mock_config_entry, 'sunset') + sunrise = IrmKmiNextSunMove(coordinator, mock_config_entry, 'sunrise') + + assert datetime.fromisoformat(sunrise.state) == datetime.fromisoformat('2023-12-27T08:44:00+01:00') + assert datetime.fromisoformat(sunset.state) == datetime.fromisoformat('2023-12-26T16:42:00+01:00')