mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Avoid polling in sun sensor entities (#100693)
This commit is contained in:
parent
86a692bb22
commit
1dadfcd52c
@ -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]
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user