Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions homeassistant/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@
"occupancy",
"power",
"temperature",
"vibration",
"window",
}
DEFAULT_INTEGRATIONS_RECOVERY_MODE = {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/anthropic/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "gold",
"quality_scale": "platinum",
"requirements": ["anthropic==0.108.0"]
}
6 changes: 2 additions & 4 deletions homeassistant/components/knx/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if (
not self._device.remote_value.readable
and (last_state := await self.async_get_last_state()) is not None
and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
):
last_state := await self.async_get_last_state()
) is not None and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.remote_value.value = XKNXDate.from_date(
dt_date.fromisoformat(last_state.state)
)
Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/knx/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if (
not self._device.remote_value.readable
and (last_state := await self.async_get_last_state()) is not None
and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
):
last_state := await self.async_get_last_state()
) is not None and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.remote_value.value = XKNXDateTime.from_datetime(
datetime.fromisoformat(last_state.state).astimezone(
dt_util.get_default_time_zone()
Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/knx/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,8 @@ class _KnxNumber(RestoreNumber):
async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if (
not self._device.sensor_value.readable
and (last_state := await self.async_get_last_state())
and (last_number_data := await self.async_get_last_number_data())
if (last_state := await self.async_get_last_state()) and (
last_number_data := await self.async_get_last_number_data()
):
if last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.sensor_value.value = last_number_data.native_value
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/knx/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if not self._device.remote_value.readable and (
last_state := await self.async_get_last_state()
):
if last_state := await self.async_get_last_state():
if (
last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
and (option := self._option_payloads.get(last_state.state)) is not None
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/knx/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ class _KnxText(TextEntity, RestoreEntity):
async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if not self._device.remote_value.readable and (
last_state := await self.async_get_last_state()
):
if last_state := await self.async_get_last_state():
if last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.remote_value.value = last_state.state

Expand Down
6 changes: 2 additions & 4 deletions homeassistant/components/knx/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@ async def async_added_to_hass(self) -> None:
"""Restore last state."""
await super().async_added_to_hass()
if (
not self._device.remote_value.readable
and (last_state := await self.async_get_last_state()) is not None
and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
):
last_state := await self.async_get_last_state()
) is not None and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
self._device.remote_value.value = XknxTime.from_time(
dt_time.fromisoformat(last_state.state)
)
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/moon/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
PLATFORMS: Final = [Platform.SENSOR]

DEFAULT_NAME: Final = "Moon"

CONF_PHASE: Final = "phase"
48 changes: 48 additions & 0 deletions homeassistant/components/moon/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Helpers for moon phases."""

from astral import moon

from homeassistant.core import callback
from homeassistant.util import dt as dt_util

STATE_FIRST_QUARTER = "first_quarter"
STATE_FULL_MOON = "full_moon"
STATE_LAST_QUARTER = "last_quarter"
STATE_NEW_MOON = "new_moon"
STATE_WANING_CRESCENT = "waning_crescent"
STATE_WANING_GIBBOUS = "waning_gibbous"
STATE_WAXING_CRESCENT = "waxing_crescent"
STATE_WAXING_GIBBOUS = "waxing_gibbous"

# The eight moon phases in chronological order (new moon to waning crescent).
MOON_PHASES: tuple[str, ...] = (
STATE_NEW_MOON,
STATE_WAXING_CRESCENT,
STATE_FIRST_QUARTER,
STATE_WAXING_GIBBOUS,
STATE_FULL_MOON,
STATE_WANING_GIBBOUS,
STATE_LAST_QUARTER,
STATE_WANING_CRESCENT,
)


@callback
def moon_phase() -> str:
"""Return the current moon phase."""
value: float = moon.phase(dt_util.now().date())
if value < 0.5 or value > 27.5:
return STATE_NEW_MOON
if value < 6.5:
return STATE_WAXING_CRESCENT
if value < 7.5:
return STATE_FIRST_QUARTER
if value < 13.5:
return STATE_WAXING_GIBBOUS
if value < 14.5:
return STATE_FULL_MOON
if value < 20.5:
return STATE_WANING_GIBBOUS
if value < 21.5:
return STATE_LAST_QUARTER
return STATE_WANING_CRESCENT
5 changes: 5 additions & 0 deletions homeassistant/components/moon/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,10 @@
}
}
}
},
"triggers": {
"phase_changed": {
"trigger": "mdi:moon-waning-crescent"
}
}
}
44 changes: 3 additions & 41 deletions homeassistant/components/moon/sensor.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
"""Support for tracking the moon phases."""

from astral import moon

from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util

from .const import DOMAIN

STATE_FIRST_QUARTER = "first_quarter"
STATE_FULL_MOON = "full_moon"
STATE_LAST_QUARTER = "last_quarter"
STATE_NEW_MOON = "new_moon"
STATE_WANING_CRESCENT = "waning_crescent"
STATE_WANING_GIBBOUS = "waning_gibbous"
STATE_WAXING_CRESCENT = "waxing_crescent"
STATE_WAXING_GIBBOUS = "waxing_gibbous"
from .helpers import MOON_PHASES, moon_phase


async def async_setup_entry(
Expand All @@ -35,16 +24,7 @@ class MoonSensorEntity(SensorEntity):

_attr_has_entity_name = True
_attr_device_class = SensorDeviceClass.ENUM
_attr_options = [
STATE_NEW_MOON,
STATE_WAXING_CRESCENT,
STATE_FIRST_QUARTER,
STATE_WAXING_GIBBOUS,
STATE_FULL_MOON,
STATE_WANING_GIBBOUS,
STATE_LAST_QUARTER,
STATE_WANING_CRESCENT,
]
_attr_options = list(MOON_PHASES)
_attr_translation_key = "phase"

def __init__(self, entry: ConfigEntry) -> None:
Expand All @@ -58,22 +38,4 @@ def __init__(self, entry: ConfigEntry) -> None:

async def async_update(self) -> None:
"""Get the time and updates the states."""
today = dt_util.now().date()
state = moon.phase(today)

if state < 0.5 or state > 27.5:
self._attr_native_value = STATE_NEW_MOON
elif state < 6.5:
self._attr_native_value = STATE_WAXING_CRESCENT
elif state < 7.5:
self._attr_native_value = STATE_FIRST_QUARTER
elif state < 13.5:
self._attr_native_value = STATE_WAXING_GIBBOUS
elif state < 14.5:
self._attr_native_value = STATE_FULL_MOON
elif state < 20.5:
self._attr_native_value = STATE_WANING_GIBBOUS
elif state < 21.5:
self._attr_native_value = STATE_LAST_QUARTER
else:
self._attr_native_value = STATE_WANING_CRESCENT
self._attr_native_value = moon_phase()
29 changes: 28 additions & 1 deletion homeassistant/components/moon/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,32 @@
}
}
},
"title": "Moon"
"selector": {
"phase": {
"options": {
"any": "Any",
"first_quarter": "[%key:component::moon::entity::sensor::phase::state::first_quarter%]",
"full_moon": "[%key:component::moon::entity::sensor::phase::state::full_moon%]",
"last_quarter": "[%key:component::moon::entity::sensor::phase::state::last_quarter%]",
"new_moon": "[%key:component::moon::entity::sensor::phase::state::new_moon%]",
"waning_crescent": "[%key:component::moon::entity::sensor::phase::state::waning_crescent%]",
"waning_gibbous": "[%key:component::moon::entity::sensor::phase::state::waning_gibbous%]",
"waxing_crescent": "[%key:component::moon::entity::sensor::phase::state::waxing_crescent%]",
"waxing_gibbous": "[%key:component::moon::entity::sensor::phase::state::waxing_gibbous%]"
}
}
},
"title": "Moon",
"triggers": {
"phase_changed": {
"description": "Triggers when the moon enters a new phase.",
"fields": {
"phase": {
"description": "Limit the trigger to a specific moon phase, or leave as Any to trigger on every phase change.",
"name": "Phase"
}
},
"name": "Moon phase changed"
}
}
}
88 changes: 88 additions & 0 deletions homeassistant/components/moon/trigger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Provides triggers for the moon."""

from datetime import datetime
from typing import cast, override

import voluptuous as vol

from homeassistant.const import CONF_OPTIONS
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_track_time_change
from homeassistant.helpers.trigger import (
Trigger,
TriggerActionRunner,
TriggerConfig,
TriggerNotTriggeredReporter,
)
from homeassistant.helpers.typing import ConfigType

from .const import CONF_PHASE
from .helpers import MOON_PHASES, moon_phase

PHASE_ANY = "any"

_PHASE_CHANGED_TRIGGER_SCHEMA = vol.Schema(
{
vol.Required(CONF_OPTIONS, default=dict): {
vol.Optional(CONF_PHASE, default=PHASE_ANY): vol.In(
[PHASE_ANY, *MOON_PHASES]
),
}
}
)


class MoonPhaseChangedTrigger(Trigger):
"""Trigger that fires when the moon enters a new phase."""

@override
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
return cast(ConfigType, _PHASE_CHANGED_TRIGGER_SCHEMA(config))

def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
"""Initialize the trigger."""
super().__init__(hass, config)
options = config.options or {}
self._phase: str = options[CONF_PHASE]

@override
async def async_attach_runner(
self,
run_action: TriggerActionRunner,
did_not_trigger: TriggerNotTriggeredReporter | None = None,
) -> CALLBACK_TYPE:
"""Attach the trigger to an action runner."""
last_phase = moon_phase()

@callback
def check_phase(_now: datetime) -> None:
nonlocal last_phase
current_phase = moon_phase()
if current_phase == last_phase:
return
previous_phase = last_phase
last_phase = current_phase
if self._phase in (PHASE_ANY, current_phase):
run_action(
{"phase": current_phase, "previous_phase": previous_phase},
"moon phase changed",
)

# The binned phase can only change when the local date rolls over.
return async_track_time_change(
self._hass, check_phase, hour=0, minute=0, second=0
)


TRIGGERS: dict[str, type[Trigger]] = {
"phase_changed": MoonPhaseChangedTrigger,
}


async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for the moon."""
return TRIGGERS
18 changes: 18 additions & 0 deletions homeassistant/components/moon/triggers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
phase_changed:
fields:
phase:
required: true
default: any
selector:
select:
translation_key: phase
options:
- any
- new_moon
- waxing_crescent
- first_quarter
- waxing_gibbous
- full_moon
- waning_gibbous
- last_quarter
- waning_crescent
Loading
Loading