1616 SensorEntity ,
1717 SensorEntityDescription ,
1818)
19- from homeassistant .const import SUN_EVENT_SUNSET , EntityCategory
20- from homeassistant .core import HomeAssistant
19+ from homeassistant .const import EntityCategory
20+ from homeassistant .core import CALLBACK_TYPE , HomeAssistant , callback
21+ from homeassistant .helpers import event
2122from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
22- from homeassistant .helpers .sun import get_astral_event_date
2323from homeassistant .util import dt as dt_util
2424
2525from .entity import (
@@ -37,15 +37,19 @@ class JewishCalendarBaseSensorDescription(SensorEntityDescription):
3737 """Base class describing Jewish Calendar sensor entities."""
3838
3939 value_fn : Callable | None
40+ next_update_fn : Callable [[Zmanim ], dt .datetime | None ] | None
4041
4142
4243@dataclass (frozen = True , kw_only = True )
4344class JewishCalendarSensorDescription (JewishCalendarBaseSensorDescription ):
4445 """Class describing Jewish Calendar sensor entities."""
4546
46- value_fn : Callable [[JewishCalendarDataResults ], str | int ]
47- attr_fn : Callable [[JewishCalendarDataResults ], dict [str , str ]] | None = None
47+ value_fn : Callable [[HDateInfo ], str | int ]
48+ attr_fn : Callable [[HDateInfo ], dict [str , str ]] | None = None
4849 options_fn : Callable [[bool ], list [str ]] | None = None
50+ next_update_fn : Callable [[Zmanim ], dt .datetime | None ] | None = (
51+ lambda zmanim : zmanim .shkia .local
52+ )
4953
5054
5155@dataclass (frozen = True , kw_only = True )
@@ -55,56 +59,52 @@ class JewishCalendarTimestampSensorDescription(JewishCalendarBaseSensorDescripti
5559 value_fn : (
5660 Callable [[HDateInfo , Callable [[dt .date ], Zmanim ]], dt .datetime | None ] | None
5761 ) = None
62+ next_update_fn : Callable [[Zmanim ], dt .datetime | None ] | None = None
5863
5964
6065INFO_SENSORS : tuple [JewishCalendarSensorDescription , ...] = (
6166 JewishCalendarSensorDescription (
6267 key = "date" ,
6368 translation_key = "hebrew_date" ,
64- value_fn = lambda results : str (results . after_shkia_date .hdate ),
65- attr_fn = lambda results : {
66- "hebrew_year" : str (results . after_shkia_date .hdate .year ),
67- "hebrew_month_name" : str (results . after_shkia_date .hdate .month ),
68- "hebrew_day" : str (results . after_shkia_date .hdate .day ),
69+ value_fn = lambda info : str (info .hdate ),
70+ attr_fn = lambda info : {
71+ "hebrew_year" : str (info .hdate .year ),
72+ "hebrew_month_name" : str (info .hdate .month ),
73+ "hebrew_day" : str (info .hdate .day ),
6974 },
7075 ),
7176 JewishCalendarSensorDescription (
7277 key = "weekly_portion" ,
7378 translation_key = "weekly_portion" ,
7479 device_class = SensorDeviceClass .ENUM ,
7580 options_fn = lambda _ : [str (p ) for p in Parasha ],
76- value_fn = lambda results : results .after_tzais_date .upcoming_shabbat .parasha ,
81+ value_fn = lambda info : info .upcoming_shabbat .parasha ,
82+ next_update_fn = lambda zmanim : zmanim .havdalah ,
7783 ),
7884 JewishCalendarSensorDescription (
7985 key = "holiday" ,
8086 translation_key = "holiday" ,
8187 device_class = SensorDeviceClass .ENUM ,
8288 options_fn = lambda diaspora : HolidayDatabase (diaspora ).get_all_names (),
83- value_fn = lambda results : ", " .join (
84- str (holiday ) for holiday in results .after_shkia_date .holidays
85- ),
86- attr_fn = lambda results : {
87- "id" : ", " .join (
88- holiday .name for holiday in results .after_shkia_date .holidays
89- ),
89+ value_fn = lambda info : ", " .join (str (holiday ) for holiday in info .holidays ),
90+ attr_fn = lambda info : {
91+ "id" : ", " .join (holiday .name for holiday in info .holidays ),
9092 "type" : ", " .join (
91- dict .fromkeys (
92- _holiday .type .name for _holiday in results .after_shkia_date .holidays
93- )
93+ dict .fromkeys (_holiday .type .name for _holiday in info .holidays )
9494 ),
9595 },
9696 ),
9797 JewishCalendarSensorDescription (
9898 key = "omer_count" ,
9999 translation_key = "omer_count" ,
100100 entity_registry_enabled_default = False ,
101- value_fn = lambda results : results . after_shkia_date .omer .total_days ,
101+ value_fn = lambda info : info .omer .total_days ,
102102 ),
103103 JewishCalendarSensorDescription (
104104 key = "daf_yomi" ,
105105 translation_key = "daf_yomi" ,
106106 entity_registry_enabled_default = False ,
107- value_fn = lambda results : results . daytime_date .daf_yomi ,
107+ value_fn = lambda info : info .daf_yomi ,
108108 ),
109109)
110110
@@ -184,26 +184,30 @@ class JewishCalendarTimestampSensorDescription(JewishCalendarBaseSensorDescripti
184184 value_fn = lambda at_date , mz : mz (
185185 at_date .upcoming_shabbat .previous_day .gdate
186186 ).candle_lighting ,
187+ next_update_fn = lambda zmanim : zmanim .havdalah ,
187188 ),
188189 JewishCalendarTimestampSensorDescription (
189190 key = "upcoming_shabbat_havdalah" ,
190191 translation_key = "upcoming_shabbat_havdalah" ,
191192 entity_registry_enabled_default = False ,
192193 value_fn = lambda at_date , mz : mz (at_date .upcoming_shabbat .gdate ).havdalah ,
194+ next_update_fn = lambda zmanim : zmanim .havdalah ,
193195 ),
194196 JewishCalendarTimestampSensorDescription (
195197 key = "upcoming_candle_lighting" ,
196198 translation_key = "upcoming_candle_lighting" ,
197199 value_fn = lambda at_date , mz : mz (
198200 at_date .upcoming_shabbat_or_yom_tov .first_day .previous_day .gdate
199201 ).candle_lighting ,
202+ next_update_fn = lambda zmanim : zmanim .havdalah ,
200203 ),
201204 JewishCalendarTimestampSensorDescription (
202205 key = "upcoming_havdalah" ,
203206 translation_key = "upcoming_havdalah" ,
204207 value_fn = lambda at_date , mz : mz (
205208 at_date .upcoming_shabbat_or_yom_tov .last_day .gdate
206209 ).havdalah ,
210+ next_update_fn = lambda zmanim : zmanim .havdalah ,
207211 ),
208212)
209213
@@ -227,46 +231,79 @@ async def async_setup_entry(
227231class JewishCalendarBaseSensor (JewishCalendarEntity , SensorEntity ):
228232 """Base class for Jewish calendar sensors."""
229233
234+ _attr_should_poll = False
230235 _attr_entity_category = EntityCategory .DIAGNOSTIC
236+ _update_unsub : CALLBACK_TYPE | None = None
231237
232- async def async_update (self ) -> None :
233- """Update the state of the sensor."""
234- now = dt_util .now ()
235- _LOGGER .debug ("Now: %s Location: %r" , now , self .data .location )
238+ entity_description : JewishCalendarBaseSensorDescription
236239
237- today = now .date ()
238- event_date = get_astral_event_date (self .hass , SUN_EVENT_SUNSET , today )
240+ async def async_added_to_hass (self ) -> None :
241+ """Call when entity is added to hass."""
242+ await super ().async_added_to_hass ()
243+ self ._schedule_update ()
244+
245+ async def async_will_remove_from_hass (self ) -> None :
246+ """Run when entity will be removed from hass."""
247+ if self ._update_unsub :
248+ self ._update_unsub ()
249+ self ._update_unsub = None
250+ return await super ().async_will_remove_from_hass ()
239251
240- if event_date is None :
241- _LOGGER .error ("Can't get sunset event date for %s" , today )
242- return
252+ def _schedule_update (self ) -> None :
253+ """Schedule the next update of the sensor."""
254+ now = dt_util .now ()
255+ zmanim = self .make_zmanim (now .date ())
256+ update = None
257+ if self .entity_description .next_update_fn :
258+ update = self .entity_description .next_update_fn (zmanim )
259+ next_midnight = dt_util .start_of_local_day () + dt .timedelta (days = 1 )
260+ if update is None or now > update :
261+ update = next_midnight
262+ if self ._update_unsub :
263+ self ._update_unsub ()
264+ self ._update_unsub = event .async_track_point_in_time (
265+ self .hass , self ._update_data , update
266+ )
243267
244- sunset = dt_util .as_local (event_date )
268+ @callback
269+ def _update_data (self , now : dt .datetime | None = None ) -> None :
270+ """Update the sensor data."""
271+ self ._update_unsub = None
272+ self ._schedule_update ()
273+ self .create_results (now )
274+ self .async_write_ha_state ()
245275
246- _LOGGER .debug ("Now: %s Sunset: %s" , now , sunset )
276+ def create_results (self , now : dt .datetime | None = None ) -> None :
277+ """Create the results for the sensor."""
278+ if now is None :
279+ now = dt_util .now ()
247280
248- daytime_date = HDateInfo ( today , diaspora = self .data .diaspora )
281+ _LOGGER . debug ( "Now: %s Location: %r" , now , self .data .location )
249282
250- # The Jewish day starts after darkness (called "tzais") and finishes at
251- # sunset ("shkia"). The time in between is a gray area
252- # (aka "Bein Hashmashot" # codespell:ignore
253- # - literally: "in between the sun and the moon").
283+ today = now . date ()
284+ zmanim = self . make_zmanim ( today )
285+ dateinfo = HDateInfo ( today , diaspora = self . data . diaspora )
286+ self . data . results = JewishCalendarDataResults ( dateinfo , zmanim )
254287
255- # For some sensors, it is more interesting to consider the date to be
256- # tomorrow based on sunset ("shkia"), for others based on "tzais".
257- # Hence the following variables.
258- after_tzais_date = after_shkia_date = daytime_date
259- today_times = self .make_zmanim ( today )
288+ def get_dateinfo ( self , now : dt . datetime | None = None ) -> HDateInfo :
289+ """Get the next date info."""
290+ if self . data . results is None :
291+ self . create_results ()
292+ assert self .data . results is not None , "Results should be available"
260293
261- if now > sunset :
262- after_shkia_date = daytime_date . next_day
294+ if now is None :
295+ now = dt_util . now ()
263296
264- if today_times .havdalah and now > today_times .havdalah :
265- after_tzais_date = daytime_date .next_day
297+ today = now .date ()
298+ zmanim = self .make_zmanim (today )
299+ update = None
300+ if self .entity_description .next_update_fn :
301+ update = self .entity_description .next_update_fn (zmanim )
266302
267- self .data .results = JewishCalendarDataResults (
268- daytime_date , after_shkia_date , after_tzais_date , today_times
269- )
303+ _LOGGER .debug ("Today: %s, update: %s" , today , update )
304+ if update is not None and now >= update :
305+ return self .data .results .dateinfo .next_day
306+ return self .data .results .dateinfo
270307
271308
272309class JewishCalendarSensor (JewishCalendarBaseSensor ):
@@ -288,18 +325,14 @@ def __init__(
288325 @property
289326 def native_value (self ) -> str | int | dt .datetime | None :
290327 """Return the state of the sensor."""
291- if self .data .results is None :
292- return None
293- return self .entity_description .value_fn (self .data .results )
328+ return self .entity_description .value_fn (self .get_dateinfo ())
294329
295330 @property
296331 def extra_state_attributes (self ) -> dict [str , str ]:
297332 """Return the state attributes."""
298- if self .data . results is None :
333+ if self .entity_description . attr_fn is None :
299334 return {}
300- if self .entity_description .attr_fn is not None :
301- return self .entity_description .attr_fn (self .data .results )
302- return {}
335+ return self .entity_description .attr_fn (self .get_dateinfo ())
303336
304337
305338class JewishCalendarTimeSensor (JewishCalendarBaseSensor ):
@@ -312,9 +345,8 @@ class JewishCalendarTimeSensor(JewishCalendarBaseSensor):
312345 def native_value (self ) -> dt .datetime | None :
313346 """Return the state of the sensor."""
314347 if self .data .results is None :
315- return None
348+ self .create_results ()
349+ assert self .data .results is not None , "Results should be available"
316350 if self .entity_description .value_fn is None :
317351 return self .data .results .zmanim .zmanim [self .entity_description .key ].local
318- return self .entity_description .value_fn (
319- self .data .results .after_tzais_date , self .make_zmanim
320- )
352+ return self .entity_description .value_fn (self .get_dateinfo (), self .make_zmanim )
0 commit comments