Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions homeassistant/components/sun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
# as we will always load it and we do not want to have
# to wait for the import executor when its busy later
# in the startup process.
from . import sensor as sensor_pre_import # noqa: F401
from . import (
binary_sensor as binary_sensor_pre_import, # noqa: F401
sensor as sensor_pre_import, # noqa: F401
)
from .const import ( # noqa: F401 # noqa: F401
DOMAIN,
STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON,
)
from .entity import Sun, SunConfigEntry

PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]

CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -52,14 +57,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: SunConfigEntry) -> bool:
await component.async_add_entities([sun])
entry.runtime_data = sun
entry.async_on_unload(sun.remove_listeners)
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR])
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: SunConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(
entry, [Platform.SENSOR]
):
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await entry.runtime_data.async_remove()
return unload_ok
100 changes: 100 additions & 0 deletions homeassistant/components/sun/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Binary Sensor platform for Sun integration."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import 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 AddConfigEntryEntitiesCallback

from .const import DOMAIN, SIGNAL_EVENTS_CHANGED
from .entity import Sun, SunConfigEntry

ENTITY_ID_BINARY_SENSOR_FORMAT = BINARY_SENSOR_DOMAIN + ".sun_{}"


@dataclass(kw_only=True, frozen=True)
class SunBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes a Sun binary sensor entity."""

value_fn: Callable[[Sun], bool | None]
signal: str


BINARY_SENSOR_TYPES: tuple[SunBinarySensorEntityDescription, ...] = (
SunBinarySensorEntityDescription(
key="solar_rising",
translation_key="solar_rising",
value_fn=lambda data: data.rising,
entity_registry_enabled_default=False,
signal=SIGNAL_EVENTS_CHANGED,
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: SunConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Sun binary sensor platform."""

sun = entry.runtime_data

async_add_entities(
[
SunBinarySensor(sun, description, entry.entry_id)
for description in BINARY_SENSOR_TYPES
]
)


class SunBinarySensor(BinarySensorEntity):
"""Representation of a Sun binary sensor."""

_attr_has_entity_name = True
_attr_should_poll = False
_attr_entity_category = EntityCategory.DIAGNOSTIC
entity_description: SunBinarySensorEntityDescription

def __init__(
self,
sun: Sun,
entity_description: SunBinarySensorEntityDescription,
entry_id: str,
) -> None:
"""Initiate Sun Binary Sensor."""
self.entity_description = entity_description
self.entity_id = ENTITY_ID_BINARY_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)},
entry_type=DeviceEntryType.SERVICE,
)

@property
def is_on(self) -> bool | None:
"""Return value of binary sensor."""
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,
)
)
9 changes: 9 additions & 0 deletions homeassistant/components/sun/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
"solar_rising": {
"default": "mdi:sun-clock"
}
},
"binary_sensor": {
"solar_rising": {
"default": "mdi:weather-sunny-off",
"state": {
"on": "mdi:weather-sunset-up",
"off": "mdi:weather-sunset-down"
}
}
}
}
}
9 changes: 9 additions & 0 deletions homeassistant/components/sun/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
"solar_azimuth": { "name": "Solar azimuth" },
"solar_elevation": { "name": "Solar elevation" },
"solar_rising": { "name": "Solar rising" }
},
"binary_sensor": {
"solar_rising": {
"name": "Solar rising",
"state": {
"on": "Rising",
"off": "Setting"
}
}
}
}
}
44 changes: 44 additions & 0 deletions tests/components/sun/test_binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""The tests for the Sun binary_sensor platform."""

from datetime import datetime, timedelta

from freezegun.api import FrozenDateTimeFactory
import pytest

from homeassistant.components import sun
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_setting_rising(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test retrieving sun setting and rising."""
utc_now = datetime(2016, 11, 1, 8, 0, 0, tzinfo=dt_util.UTC)
freezer.move_to(utc_now)
await async_setup_component(hass, sun.DOMAIN, {sun.DOMAIN: {}})
await hass.async_block_till_done()

assert hass.states.get("binary_sensor.sun_solar_rising").state == "on"

entry_ids = hass.config_entries.async_entries("sun")

freezer.tick(timedelta(hours=12))
# 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 hass.states.get("binary_sensor.sun_solar_rising").state == "off"

entity = entity_registry.async_get("binary_sensor.sun_solar_rising")
assert entity
assert entity.entity_category is EntityCategory.DIAGNOSTIC
assert entity.unique_id == f"{entry_ids[0].entry_id}-solar_rising"