mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 17:57:55 +00:00
Add sensor platform to Sun (#81045)
* Sun sensor * remove extra attr * Add tests * Add back attributes * position sensors disabled default * entity id * unique id * test init to attributes * Fix test init * Fix test sensor * test unique id * uom * remove rising * Remove not needed uom property * Fix reload issue * degree
This commit is contained in:
parent
7f6406127e
commit
92beb48a41
@ -12,6 +12,7 @@ from homeassistant.const import (
|
|||||||
EVENT_CORE_CONFIG_UPDATE,
|
EVENT_CORE_CONFIG_UPDATE,
|
||||||
SUN_EVENT_SUNRISE,
|
SUN_EVENT_SUNRISE,
|
||||||
SUN_EVENT_SUNSET,
|
SUN_EVENT_SUNSET,
|
||||||
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers import event
|
from homeassistant.helpers import event
|
||||||
@ -97,15 +98,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
# we will create entities before firing EVENT_COMPONENT_LOADED
|
# we will create entities before firing EVENT_COMPONENT_LOADED
|
||||||
await async_process_integration_platform_for_component(hass, DOMAIN)
|
await async_process_integration_platform_for_component(hass, DOMAIN)
|
||||||
hass.data[DOMAIN] = Sun(hass)
|
hass.data[DOMAIN] = Sun(hass)
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
sun = hass.data.pop(DOMAIN)
|
if unload_ok := await hass.config_entries.async_unload_platforms(
|
||||||
sun.remove_listeners()
|
entry, [Platform.SENSOR]
|
||||||
hass.states.async_remove(sun.entity_id)
|
):
|
||||||
return True
|
sun: Sun = hass.data.pop(DOMAIN)
|
||||||
|
sun.remove_listeners()
|
||||||
|
hass.states.async_remove(sun.entity_id)
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
class Sun(Entity):
|
class Sun(Entity):
|
||||||
|
133
homeassistant/components/sun/sensor.py
Normal file
133
homeassistant/components/sun/sensor.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
"""Sensor platform for Sun integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import DEGREE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
|
from . import Sun
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
ENTITY_ID_SENSOR_FORMAT = SENSOR_DOMAIN + ".sun_{}"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SunEntityDescriptionMixin:
|
||||||
|
"""Mixin for required Sun base description keys."""
|
||||||
|
|
||||||
|
value_fn: Callable[[Sun], StateType | datetime]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SunSensorEntityDescription(SensorEntityDescription, SunEntityDescriptionMixin):
|
||||||
|
"""Describes Sun sensor entity."""
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[SunSensorEntityDescription, ...] = (
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_dawn",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next dawn",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_dawn,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_dusk",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next dusk",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_dusk,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_midnight",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next midnight",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_midnight,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_noon",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next noon",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_noon,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_rising",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next rising",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_rising,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="next_setting",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
name="Next setting",
|
||||||
|
icon="mdi:sun-clock",
|
||||||
|
value_fn=lambda data: data.next_setting,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="solar_elevation",
|
||||||
|
name="Solar elevation",
|
||||||
|
icon="mdi:theme-light-dark",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data.solar_elevation,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
),
|
||||||
|
SunSensorEntityDescription(
|
||||||
|
key="solar_azimuth",
|
||||||
|
name="Solar azimuth",
|
||||||
|
icon="mdi:sun-angle",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data.solar_azimuth,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up Sun sensor platform."""
|
||||||
|
|
||||||
|
sun: Sun = hass.data[DOMAIN]
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[SunSensor(sun, description, entry.entry_id) for description in SENSOR_TYPES]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SunSensor(SensorEntity):
|
||||||
|
"""Representation of a Sun Sensor."""
|
||||||
|
|
||||||
|
entity_description: SunSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, sun: Sun, entity_description: SunSensorEntityDescription, entry_id: str
|
||||||
|
) -> None:
|
||||||
|
"""Initiate Sun Sensor."""
|
||||||
|
self.entity_description = entity_description
|
||||||
|
self.entity_id = ENTITY_ID_SENSOR_FORMAT.format(entity_description.key)
|
||||||
|
self._attr_unique_id = f"{entry_id}-{entity_description.key}"
|
||||||
|
self.sun = sun
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> StateType | datetime:
|
||||||
|
"""Return value of sensor."""
|
||||||
|
state = self.entity_description.value_fn(self.sun)
|
||||||
|
return state
|
@ -5,10 +5,9 @@ from unittest.mock import patch
|
|||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import homeassistant.components.sun as sun
|
from homeassistant.components import sun
|
||||||
from homeassistant.const import EVENT_STATE_CHANGED
|
from homeassistant.const import EVENT_STATE_CHANGED
|
||||||
import homeassistant.core as ha
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -196,7 +195,7 @@ async def test_state_change_count(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
events = []
|
events = []
|
||||||
|
|
||||||
@ha.callback
|
@callback
|
||||||
def state_change_listener(event):
|
def state_change_listener(event):
|
||||||
if event.data.get("entity_id") == "sun.sun":
|
if event.data.get("entity_id") == "sun.sun":
|
||||||
events.append(event)
|
events.append(event)
|
||||||
|
101
tests/components/sun/test_sensor.py
Normal file
101
tests/components/sun/test_sensor.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"""The tests for the Sun sensor platform."""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from astral import LocationInfo
|
||||||
|
import astral.sun
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from homeassistant.components import sun
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
import homeassistant.helpers.entity_registry as er
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setting_rising(hass: HomeAssistant) -> 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: {}})
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
utc_today = utc_now.date()
|
||||||
|
|
||||||
|
location = LocationInfo(
|
||||||
|
latitude=hass.config.latitude, longitude=hass.config.longitude
|
||||||
|
)
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_dawn = astral.sun.dawn(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_dawn > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_dusk = astral.sun.dusk(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_dusk > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_midnight = astral.sun.midnight(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_midnight > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_noon = astral.sun.noon(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_noon > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_rising = astral.sun.sunrise(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_rising > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
mod = -1
|
||||||
|
while True:
|
||||||
|
next_setting = astral.sun.sunset(
|
||||||
|
location.observer, date=utc_today + timedelta(days=mod)
|
||||||
|
)
|
||||||
|
if next_setting > utc_now:
|
||||||
|
break
|
||||||
|
mod += 1
|
||||||
|
|
||||||
|
state1 = hass.states.get("sensor.sun_next_dawn")
|
||||||
|
state2 = hass.states.get("sensor.sun_next_dusk")
|
||||||
|
state3 = hass.states.get("sensor.sun_next_midnight")
|
||||||
|
state4 = hass.states.get("sensor.sun_next_noon")
|
||||||
|
state5 = hass.states.get("sensor.sun_next_rising")
|
||||||
|
state6 = hass.states.get("sensor.sun_next_setting")
|
||||||
|
assert next_dawn.replace(microsecond=0) == dt_util.parse_datetime(state1.state)
|
||||||
|
assert next_dusk.replace(microsecond=0) == dt_util.parse_datetime(state2.state)
|
||||||
|
assert next_midnight.replace(microsecond=0) == dt_util.parse_datetime(state3.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_setting.replace(microsecond=0) == dt_util.parse_datetime(state6.state)
|
||||||
|
|
||||||
|
entry_ids = hass.config_entries.async_entries("sun")
|
||||||
|
|
||||||
|
entity_reg = er.async_get(hass)
|
||||||
|
entity = entity_reg.async_get("sensor.sun_next_dawn")
|
||||||
|
|
||||||
|
assert entity.unique_id == f"{entry_ids[0].entry_id}-next_dawn"
|
Loading…
x
Reference in New Issue
Block a user