Avoid polling in sun sensor entities (#100693)

This commit is contained in:
J. Nick Koston 2023-09-22 11:16:37 +02:00 committed by GitHub
parent 86a692bb22
commit 1dadfcd52c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 10 deletions

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
)
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, event
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.sun import (
get_astral_location,
@ -24,7 +25,7 @@ from homeassistant.helpers.sun import (
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
from .const import DOMAIN
from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED
_LOGGER = logging.getLogger(__name__)
@ -285,6 +286,7 @@ class Sun(Entity):
if self._update_sun_position_listener:
self._update_sun_position_listener()
self.update_sun_position()
async_dispatcher_send(self.hass, SIGNAL_EVENTS_CHANGED)
# Set timer for the next solar event
self._update_events_listener = event.async_track_point_in_utc_time(
@ -312,6 +314,8 @@ class Sun(Entity):
)
self.async_write_ha_state()
async_dispatcher_send(self.hass, SIGNAL_POSITION_CHANGED)
# Next update as per the current phase
assert self.phase
delta = _PHASE_UPDATES[self.phase]

View File

@ -4,3 +4,6 @@ from typing import Final
DOMAIN: Final = "sun"
DEFAULT_NAME: Final = "Sun"
SIGNAL_POSITION_CHANGED = f"{DOMAIN}_position_changed"
SIGNAL_EVENTS_CHANGED = f"{DOMAIN}_events_changed"

View File

@ -16,11 +16,12 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEGREE, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import Sun
from .const import DOMAIN
from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".sun_{}"
@ -30,6 +31,7 @@ class SunEntityDescriptionMixin:
"""Mixin for required Sun base description keys."""
value_fn: Callable[[Sun], StateType | datetime]
signal: str
@dataclass
@ -44,6 +46,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_dawn",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_dawn,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="next_dusk",
@ -51,6 +54,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_dusk",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_dusk,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="next_midnight",
@ -58,6 +62,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_midnight",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_midnight,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="next_noon",
@ -65,6 +70,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_noon",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_noon,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="next_rising",
@ -72,6 +78,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_rising",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_rising,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="next_setting",
@ -79,6 +86,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
translation_key="next_setting",
icon="mdi:sun-clock",
value_fn=lambda data: data.next_setting,
signal=SIGNAL_EVENTS_CHANGED,
),
SunSensorEntityDescription(
key="solar_elevation",
@ -88,6 +96,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
value_fn=lambda data: data.solar_elevation,
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
signal=SIGNAL_POSITION_CHANGED,
),
SunSensorEntityDescription(
key="solar_azimuth",
@ -97,6 +106,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
value_fn=lambda data: data.solar_azimuth,
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
signal=SIGNAL_POSITION_CHANGED,
),
)
@ -117,6 +127,7 @@ class SunSensor(SensorEntity):
"""Representation of a Sun Sensor."""
_attr_has_entity_name = True
_attr_should_poll = False
_attr_entity_category = EntityCategory.DIAGNOSTIC
entity_description: SunSensorEntityDescription
@ -128,7 +139,6 @@ class SunSensor(SensorEntity):
self.entity_id = ENTITY_ID_SENSOR_FORMAT.format(entity_description.key)
self._attr_unique_id = f"{entry_id}-{entity_description.key}"
self.sun = sun
self._attr_device_info = DeviceInfo(
name="Sun",
identifiers={(DOMAIN, entry_id)},
@ -138,5 +148,15 @@ class SunSensor(SensorEntity):
@property
def native_value(self) -> StateType | datetime:
"""Return value of sensor."""
state = self.entity_description.value_fn(self.sun)
return state
return self.entity_description.value_fn(self.sun)
async def async_added_to_hass(self) -> None:
"""Register signal listener when added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
async_dispatcher_connect(
self.hass,
self.entity_description.signal,
self.async_write_ha_state,
)
)

View File

@ -3,7 +3,8 @@ from datetime import datetime, timedelta
from astral import LocationInfo
import astral.sun
from freezegun import freeze_time
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components import sun
from homeassistant.const import EntityCategory
@ -13,12 +14,15 @@ from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
async def test_setting_rising(hass: HomeAssistant) -> None:
async def test_setting_rising(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
entity_registry_enabled_by_default: None,
) -> None:
"""Test retrieving sun setting and rising."""
utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC)
with freeze_time(utc_now):
await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}})
freezer.move_to(utc_now)
await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}})
await hass.async_block_till_done()
utc_today = utc_now.date()
@ -81,6 +85,9 @@ async def test_setting_rising(hass: HomeAssistant) -> None:
break
mod += 1
expected_solar_elevation = astral.sun.elevation(location.observer, utc_now)
expected_solar_azimuth = astral.sun.azimuth(location.observer, utc_now)
state1 = hass.states.get("sensor.sun_next_dawn")
state2 = hass.states.get("sensor.sun_next_dusk")
state3 = hass.states.get("sensor.sun_next_midnight")
@ -93,6 +100,14 @@ async def test_setting_rising(hass: HomeAssistant) -> None:
assert next_noon.replace(microsecond=0) == dt_util.parse_datetime(state4.state)
assert next_rising.replace(microsecond=0) == dt_util.parse_datetime(state5.state)
assert next_setting.replace(microsecond=0) == dt_util.parse_datetime(state6.state)
solar_elevation_state = hass.states.get("sensor.sun_solar_elevation")
assert float(solar_elevation_state.state) == pytest.approx(
expected_solar_elevation, 0.1
)
solar_azimuth_state = hass.states.get("sensor.sun_solar_azimuth")
assert float(solar_azimuth_state.state) == pytest.approx(
expected_solar_azimuth, 0.1
)
entry_ids = hass.config_entries.async_entries("sun")
@ -102,3 +117,24 @@ async def test_setting_rising(hass: HomeAssistant) -> None:
assert entity
assert entity.entity_category is EntityCategory.DIAGNOSTIC
assert entity.unique_id == f"{entry_ids[0].entry_id}-next_dawn"
freezer.tick(timedelta(hours=24))
# Block once for Sun to update
await hass.async_block_till_done()
# Block another time for the sensors to update
await hass.async_block_till_done()
# Make sure all the signals work
assert state1.state != hass.states.get("sensor.sun_next_dawn").state
assert state2.state != hass.states.get("sensor.sun_next_dusk").state
assert state3.state != hass.states.get("sensor.sun_next_midnight").state
assert state4.state != hass.states.get("sensor.sun_next_noon").state
assert state5.state != hass.states.get("sensor.sun_next_rising").state
assert state6.state != hass.states.get("sensor.sun_next_setting").state
assert (
solar_elevation_state.state
!= hass.states.get("sensor.sun_solar_elevation").state
)
assert (
solar_azimuth_state.state != hass.states.get("sensor.sun_solar_azimuth").state
)