Add next sunset sunrise sensors

This commit is contained in:
Jules 2024-06-09 20:37:14 +02:00
parent 0059b2f78f
commit f1e7c267e6
Signed by: jdejaegh
GPG key ID: 99D6D184CA66933A
7 changed files with 100 additions and 2 deletions

View file

@ -176,7 +176,8 @@ class IrmKmiCoordinator(TimestampDataUpdateCoordinator):
radar_forecast=IrmKmiCoordinator.radar_list_to_forecast(api_data.get('animation', {})), radar_forecast=IrmKmiCoordinator.radar_list_to_forecast(api_data.get('animation', {})),
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')),
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, async def download_images_from_api(self,

View file

@ -73,3 +73,4 @@ class ProcessedCoordinatorData(TypedDict, total=False):
animation: RadarAnimationData animation: RadarAnimationData
warnings: List[WarningData] warnings: List[WarningData]
pollen: dict pollen: dict
country: str

View file

@ -12,6 +12,7 @@ from homeassistant.util import dt
from custom_components.irm_kmi import DOMAIN, IrmKmiCoordinator 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.const import POLLEN_NAMES, POLLEN_TO_ICON_MAP
from custom_components.irm_kmi.data import IrmKmiForecast
from custom_components.irm_kmi.pollen import PollenParser from custom_components.irm_kmi.pollen import PollenParser
_LOGGER = logging.getLogger(__name__) _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([IrmKmiPollen(coordinator, entry, pollen.lower()) for pollen in POLLEN_NAMES])
async_add_entities([IrmKmiNextWarning(coordinator, entry),]) 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): class IrmKmiPollen(CoordinatorEntity, SensorEntity):
"""Representation of a pollen sensor""" """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'] != '']) [warning['friendly_name'] for warning in attrs['next_warnings'] if warning['friendly_name'] != ''])
return attrs 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

View file

@ -93,6 +93,12 @@
"next_warning": { "next_warning": {
"name": "Next warning" "name": "Next warning"
}, },
"next_sunrise": {
"name": "Next sunrise"
},
"next_sunset": {
"name": "Next sunset"
},
"pollen_alder": { "pollen_alder": {
"name": "Alder pollen", "name": "Alder pollen",
"state": { "state": {

View file

@ -93,6 +93,12 @@
"next_warning": { "next_warning": {
"name": "Prochain avertissement" "name": "Prochain avertissement"
}, },
"next_sunrise": {
"name": "Prochain lever de soleil"
},
"next_sunset": {
"name": "Prochain coucher de soleil"
},
"pollen_alder": { "pollen_alder": {
"name": "Pollen d'aulne", "name": "Pollen d'aulne",
"state": { "state": {

View file

@ -93,6 +93,12 @@
"next_warning": { "next_warning": {
"name": "Volgende waarschuwing" "name": "Volgende waarschuwing"
}, },
"next_sunrise": {
"name": "Volgende zonsopkomst"
},
"next_sunset": {
"name": "Volgende zonsondergang"
},
"pollen_alder": { "pollen_alder": {
"name": "Elzenpollen", "name": "Elzenpollen",
"state": { "state": {

View file

@ -7,7 +7,7 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.irm_kmi import IrmKmiCoordinator from custom_components.irm_kmi import IrmKmiCoordinator
from custom_components.irm_kmi.binary_sensor import IrmKmiWarning from custom_components.irm_kmi.binary_sensor import IrmKmiWarning
from custom_components.irm_kmi.const import CONF_LANGUAGE_OVERRIDE 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 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 len(warning.extra_state_attributes['next_warnings']) == 0
assert warning.extra_state_attributes['next_warnings_friendly_names'] == "" 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')