mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +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.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, event
|
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.entity import Entity
|
||||||
from homeassistant.helpers.sun import (
|
from homeassistant.helpers.sun import (
|
||||||
get_astral_location,
|
get_astral_location,
|
||||||
@ -24,7 +25,7 @@ from homeassistant.helpers.sun import (
|
|||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util import dt as dt_util
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -285,6 +286,7 @@ class Sun(Entity):
|
|||||||
if self._update_sun_position_listener:
|
if self._update_sun_position_listener:
|
||||||
self._update_sun_position_listener()
|
self._update_sun_position_listener()
|
||||||
self.update_sun_position()
|
self.update_sun_position()
|
||||||
|
async_dispatcher_send(self.hass, SIGNAL_EVENTS_CHANGED)
|
||||||
|
|
||||||
# Set timer for the next solar event
|
# Set timer for the next solar event
|
||||||
self._update_events_listener = event.async_track_point_in_utc_time(
|
self._update_events_listener = event.async_track_point_in_utc_time(
|
||||||
@ -312,6 +314,8 @@ class Sun(Entity):
|
|||||||
)
|
)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async_dispatcher_send(self.hass, SIGNAL_POSITION_CHANGED)
|
||||||
|
|
||||||
# Next update as per the current phase
|
# Next update as per the current phase
|
||||||
assert self.phase
|
assert self.phase
|
||||||
delta = _PHASE_UPDATES[self.phase]
|
delta = _PHASE_UPDATES[self.phase]
|
||||||
|
@ -4,3 +4,6 @@ from typing import Final
|
|||||||
DOMAIN: Final = "sun"
|
DOMAIN: Final = "sun"
|
||||||
|
|
||||||
DEFAULT_NAME: 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.const import DEGREE, EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
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.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
from . import Sun
|
from . import Sun
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, SIGNAL_EVENTS_CHANGED, SIGNAL_POSITION_CHANGED
|
||||||
|
|
||||||
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".sun_{}"
|
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".sun_{}"
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ class SunEntityDescriptionMixin:
|
|||||||
"""Mixin for required Sun base description keys."""
|
"""Mixin for required Sun base description keys."""
|
||||||
|
|
||||||
value_fn: Callable[[Sun], StateType | datetime]
|
value_fn: Callable[[Sun], StateType | datetime]
|
||||||
|
signal: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -44,6 +46,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_dawn",
|
translation_key="next_dawn",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_dawn,
|
value_fn=lambda data: data.next_dawn,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="next_dusk",
|
key="next_dusk",
|
||||||
@ -51,6 +54,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_dusk",
|
translation_key="next_dusk",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_dusk,
|
value_fn=lambda data: data.next_dusk,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="next_midnight",
|
key="next_midnight",
|
||||||
@ -58,6 +62,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_midnight",
|
translation_key="next_midnight",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_midnight,
|
value_fn=lambda data: data.next_midnight,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="next_noon",
|
key="next_noon",
|
||||||
@ -65,6 +70,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_noon",
|
translation_key="next_noon",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_noon,
|
value_fn=lambda data: data.next_noon,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="next_rising",
|
key="next_rising",
|
||||||
@ -72,6 +78,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_rising",
|
translation_key="next_rising",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_rising,
|
value_fn=lambda data: data.next_rising,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="next_setting",
|
key="next_setting",
|
||||||
@ -79,6 +86,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
translation_key="next_setting",
|
translation_key="next_setting",
|
||||||
icon="mdi:sun-clock",
|
icon="mdi:sun-clock",
|
||||||
value_fn=lambda data: data.next_setting,
|
value_fn=lambda data: data.next_setting,
|
||||||
|
signal=SIGNAL_EVENTS_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="solar_elevation",
|
key="solar_elevation",
|
||||||
@ -88,6 +96,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
value_fn=lambda data: data.solar_elevation,
|
value_fn=lambda data: data.solar_elevation,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=DEGREE,
|
native_unit_of_measurement=DEGREE,
|
||||||
|
signal=SIGNAL_POSITION_CHANGED,
|
||||||
),
|
),
|
||||||
SunSensorEntityDescription(
|
SunSensorEntityDescription(
|
||||||
key="solar_azimuth",
|
key="solar_azimuth",
|
||||||
@ -97,6 +106,7 @@ SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
|||||||
value_fn=lambda data: data.solar_azimuth,
|
value_fn=lambda data: data.solar_azimuth,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
native_unit_of_measurement=DEGREE,
|
native_unit_of_measurement=DEGREE,
|
||||||
|
signal=SIGNAL_POSITION_CHANGED,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,6 +127,7 @@ class SunSensor(SensorEntity):
|
|||||||
"""Representation of a Sun Sensor."""
|
"""Representation of a Sun Sensor."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_should_poll = False
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
entity_description: SunSensorEntityDescription
|
entity_description: SunSensorEntityDescription
|
||||||
|
|
||||||
@ -128,7 +139,6 @@ class SunSensor(SensorEntity):
|
|||||||
self.entity_id = ENTITY_ID_SENSOR_FORMAT.format(entity_description.key)
|
self.entity_id = ENTITY_ID_SENSOR_FORMAT.format(entity_description.key)
|
||||||
self._attr_unique_id = f"{entry_id}-{entity_description.key}"
|
self._attr_unique_id = f"{entry_id}-{entity_description.key}"
|
||||||
self.sun = sun
|
self.sun = sun
|
||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
name="Sun",
|
name="Sun",
|
||||||
identifiers={(DOMAIN, entry_id)},
|
identifiers={(DOMAIN, entry_id)},
|
||||||
@ -138,5 +148,15 @@ class SunSensor(SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> StateType | datetime:
|
def native_value(self) -> StateType | datetime:
|
||||||
"""Return value of sensor."""
|
"""Return value of sensor."""
|
||||||
state = self.entity_description.value_fn(self.sun)
|
return self.entity_description.value_fn(self.sun)
|
||||||
return state
|
|
||||||
|
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
|
from astral import LocationInfo
|
||||||
import astral.sun
|
import astral.sun
|
||||||
from freezegun import freeze_time
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import sun
|
from homeassistant.components import sun
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
@ -13,12 +14,15 @@ from homeassistant.setup import async_setup_component
|
|||||||
import homeassistant.util.dt as dt_util
|
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."""
|
"""Test retrieving sun setting and rising."""
|
||||||
utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC)
|
utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC)
|
||||||
with freeze_time(utc_now):
|
freezer.move_to(utc_now)
|
||||||
await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}})
|
await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}})
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
utc_today = utc_now.date()
|
utc_today = utc_now.date()
|
||||||
@ -81,6 +85,9 @@ async def test_setting_rising(hass: HomeAssistant) -> None:
|
|||||||
break
|
break
|
||||||
mod += 1
|
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")
|
state1 = hass.states.get("sensor.sun_next_dawn")
|
||||||
state2 = hass.states.get("sensor.sun_next_dusk")
|
state2 = hass.states.get("sensor.sun_next_dusk")
|
||||||
state3 = hass.states.get("sensor.sun_next_midnight")
|
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_noon.replace(microsecond=0) == dt_util.parse_datetime(state4.state)
|
||||||
assert next_rising.replace(microsecond=0) == dt_util.parse_datetime(state5.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)
|
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")
|
entry_ids = hass.config_entries.async_entries("sun")
|
||||||
|
|
||||||
@ -102,3 +117,24 @@ async def test_setting_rising(hass: HomeAssistant) -> None:
|
|||||||
assert entity
|
assert entity
|
||||||
assert entity.entity_category is EntityCategory.DIAGNOSTIC
|
assert entity.entity_category is EntityCategory.DIAGNOSTIC
|
||||||
assert entity.unique_id == f"{entry_ids[0].entry_id}-next_dawn"
|
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