Files
core/homeassistant/components/jewish_calendar/sensor.py
2025-06-10 21:25:47 +02:00

325 lines
11 KiB
Python

"""Support for Jewish calendar sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import datetime as dt
import logging
from hdate import HDateInfo, Zmanim
from hdate.holidays import HolidayDatabase
from hdate.parasha import Parasha
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import SUN_EVENT_SUNSET, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.sun import get_astral_event_date
from homeassistant.util import dt as dt_util
from .entity import (
JewishCalendarConfigEntry,
JewishCalendarDataResults,
JewishCalendarEntity,
)
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class JewishCalendarBaseSensorDescription(SensorEntityDescription):
"""Base class describing Jewish Calendar sensor entities."""
value_fn: Callable | None
@dataclass(frozen=True, kw_only=True)
class JewishCalendarSensorDescription(JewishCalendarBaseSensorDescription):
"""Class describing Jewish Calendar sensor entities."""
value_fn: Callable[[JewishCalendarDataResults], str | int]
attr_fn: Callable[[JewishCalendarDataResults], dict[str, str]] | None = None
options_fn: Callable[[bool], list[str]] | None = None
@dataclass(frozen=True, kw_only=True)
class JewishCalendarTimestampSensorDescription(JewishCalendarBaseSensorDescription):
"""Class describing Jewish Calendar sensor timestamp entities."""
value_fn: (
Callable[[HDateInfo, Callable[[dt.date], Zmanim]], dt.datetime | None] | None
) = None
INFO_SENSORS: tuple[JewishCalendarSensorDescription, ...] = (
JewishCalendarSensorDescription(
key="date",
translation_key="hebrew_date",
value_fn=lambda results: str(results.after_shkia_date.hdate),
attr_fn=lambda results: {
"hebrew_year": str(results.after_shkia_date.hdate.year),
"hebrew_month_name": str(results.after_shkia_date.hdate.month),
"hebrew_day": str(results.after_shkia_date.hdate.day),
},
),
JewishCalendarSensorDescription(
key="weekly_portion",
translation_key="weekly_portion",
device_class=SensorDeviceClass.ENUM,
options_fn=lambda _: [str(p) for p in Parasha],
value_fn=lambda results: str(results.after_tzais_date.upcoming_shabbat.parasha),
),
JewishCalendarSensorDescription(
key="holiday",
translation_key="holiday",
device_class=SensorDeviceClass.ENUM,
options_fn=lambda diaspora: HolidayDatabase(diaspora).get_all_names(),
value_fn=lambda results: ", ".join(
str(holiday) for holiday in results.after_shkia_date.holidays
),
attr_fn=lambda results: {
"id": ", ".join(
holiday.name for holiday in results.after_shkia_date.holidays
),
"type": ", ".join(
dict.fromkeys(
_holiday.type.name for _holiday in results.after_shkia_date.holidays
)
),
},
),
JewishCalendarSensorDescription(
key="omer_count",
translation_key="omer_count",
entity_registry_enabled_default=False,
value_fn=lambda results: (
results.after_shkia_date.omer.total_days
if results.after_shkia_date.omer
else 0
),
),
JewishCalendarSensorDescription(
key="daf_yomi",
translation_key="daf_yomi",
entity_registry_enabled_default=False,
value_fn=lambda results: str(results.daytime_date.daf_yomi),
),
)
TIME_SENSORS: tuple[JewishCalendarTimestampSensorDescription, ...] = (
JewishCalendarTimestampSensorDescription(
key="alot_hashachar",
translation_key="alot_hashachar",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="talit_and_tefillin",
translation_key="talit_and_tefillin",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="netz_hachama",
translation_key="netz_hachama",
),
JewishCalendarTimestampSensorDescription(
key="sof_zman_shema_gra",
translation_key="sof_zman_shema_gra",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="sof_zman_shema_mga",
translation_key="sof_zman_shema_mga",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="sof_zman_tfilla_gra",
translation_key="sof_zman_tfilla_gra",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="sof_zman_tfilla_mga",
translation_key="sof_zman_tfilla_mga",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="chatzot_hayom",
translation_key="chatzot_hayom",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="mincha_gedola",
translation_key="mincha_gedola",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="mincha_ketana",
translation_key="mincha_ketana",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="plag_hamincha",
translation_key="plag_hamincha",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="shkia",
translation_key="shkia",
),
JewishCalendarTimestampSensorDescription(
key="tset_hakohavim_tsom",
translation_key="tset_hakohavim_tsom",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="tset_hakohavim_shabbat",
translation_key="tset_hakohavim_shabbat",
entity_registry_enabled_default=False,
),
JewishCalendarTimestampSensorDescription(
key="upcoming_shabbat_candle_lighting",
translation_key="upcoming_shabbat_candle_lighting",
entity_registry_enabled_default=False,
value_fn=lambda at_date, mz: mz(
at_date.upcoming_shabbat.previous_day.gdate
).candle_lighting,
),
JewishCalendarTimestampSensorDescription(
key="upcoming_shabbat_havdalah",
translation_key="upcoming_shabbat_havdalah",
entity_registry_enabled_default=False,
value_fn=lambda at_date, mz: mz(at_date.upcoming_shabbat.gdate).havdalah,
),
JewishCalendarTimestampSensorDescription(
key="upcoming_candle_lighting",
translation_key="upcoming_candle_lighting",
value_fn=lambda at_date, mz: mz(
at_date.upcoming_shabbat_or_yom_tov.first_day.previous_day.gdate
).candle_lighting,
),
JewishCalendarTimestampSensorDescription(
key="upcoming_havdalah",
translation_key="upcoming_havdalah",
value_fn=lambda at_date, mz: mz(
at_date.upcoming_shabbat_or_yom_tov.last_day.gdate
).havdalah,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: JewishCalendarConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Jewish calendar sensors ."""
sensors: list[JewishCalendarBaseSensor] = [
JewishCalendarSensor(config_entry, description) for description in INFO_SENSORS
]
sensors.extend(
JewishCalendarTimeSensor(config_entry, description)
for description in TIME_SENSORS
)
async_add_entities(sensors, update_before_add=True)
class JewishCalendarBaseSensor(JewishCalendarEntity, SensorEntity):
"""Base class for Jewish calendar sensors."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
async def async_update(self) -> None:
"""Update the state of the sensor."""
now = dt_util.now()
_LOGGER.debug("Now: %s Location: %r", now, self.data.location)
today = now.date()
event_date = get_astral_event_date(self.hass, SUN_EVENT_SUNSET, today)
if event_date is None:
_LOGGER.error("Can't get sunset event date for %s", today)
return
sunset = dt_util.as_local(event_date)
_LOGGER.debug("Now: %s Sunset: %s", now, sunset)
daytime_date = HDateInfo(today, diaspora=self.data.diaspora)
# The Jewish day starts after darkness (called "tzais") and finishes at
# sunset ("shkia"). The time in between is a gray area
# (aka "Bein Hashmashot" # codespell:ignore
# - literally: "in between the sun and the moon").
# For some sensors, it is more interesting to consider the date to be
# tomorrow based on sunset ("shkia"), for others based on "tzais".
# Hence the following variables.
after_tzais_date = after_shkia_date = daytime_date
today_times = self.make_zmanim(today)
if now > sunset:
after_shkia_date = daytime_date.next_day
if today_times.havdalah and now > today_times.havdalah:
after_tzais_date = daytime_date.next_day
self.data.results = JewishCalendarDataResults(
daytime_date, after_shkia_date, after_tzais_date, today_times
)
class JewishCalendarSensor(JewishCalendarBaseSensor):
"""Representation of an Jewish calendar sensor."""
entity_description: JewishCalendarSensorDescription
def __init__(
self,
config_entry: JewishCalendarConfigEntry,
description: SensorEntityDescription,
) -> None:
"""Initialize the Jewish calendar sensor."""
super().__init__(config_entry, description)
# Set the options for enumeration sensors
if self.entity_description.options_fn is not None:
self._attr_options = self.entity_description.options_fn(self.data.diaspora)
@property
def native_value(self) -> str | int | dt.datetime | None:
"""Return the state of the sensor."""
if self.data.results is None:
return None
return self.entity_description.value_fn(self.data.results)
@property
def extra_state_attributes(self) -> dict[str, str]:
"""Return the state attributes."""
if self.data.results is None:
return {}
if self.entity_description.attr_fn is not None:
return self.entity_description.attr_fn(self.data.results)
return {}
class JewishCalendarTimeSensor(JewishCalendarBaseSensor):
"""Implement attributes for sensors returning times."""
_attr_device_class = SensorDeviceClass.TIMESTAMP
entity_description: JewishCalendarTimestampSensorDescription
@property
def native_value(self) -> dt.datetime | None:
"""Return the state of the sensor."""
if self.data.results is None:
return None
if self.entity_description.value_fn is None:
return self.data.results.zmanim.zmanim[self.entity_description.key].local
return self.entity_description.value_fn(
self.data.results.after_tzais_date, self.make_zmanim
)