Skip to content

Commit

Permalink
ATTR_NEXT_TOGGLES
Browse files Browse the repository at this point in the history
  • Loading branch information
amitfin committed Jan 10, 2025
1 parent dcb778c commit 5d2553d
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 4 deletions.
9 changes: 8 additions & 1 deletion custom_components/daily_schedule/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@

from .const import (
ATTR_NEXT_TOGGLE,
ATTR_NEXT_TOGGLES,
CONF_DISABLED,
CONF_FROM,
CONF_SCHEDULE,
CONF_TO,
CONF_UTC,
NEXT_TOGGLES_COUNT,
SERVICE_SET,
)
from .schedule import Schedule
Expand Down Expand Up @@ -69,6 +71,9 @@ class DailyScheduleSensor(BinarySensorEntity):
_attr_has_entity_name = True
_attr_should_poll = False
_attr_icon = "mdi:timetable"
_entity_component_unrecorded_attributes = frozenset(
{ATTR_NEXT_TOGGLE, ATTR_NEXT_TOGGLES}
)

def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize object with defaults."""
Expand Down Expand Up @@ -115,8 +120,10 @@ async def async_set(self, schedule: list[dict[str, Any]]) -> None:
def _update_state(self, _: datetime.datetime | None = None) -> None:
"""Update the state and schedule next update."""
self._clean_up_listener()
next_update = self._schedule.next_update(self._now())
next_toggles = self._schedule.next_updates(self._now(), NEXT_TOGGLES_COUNT)
next_update = next_toggles[0] if len(next_toggles) > 0 else None
self._attr_extra_state_attributes[ATTR_NEXT_TOGGLE] = next_update
self._attr_extra_state_attributes[ATTR_NEXT_TOGGLES] = next_toggles
self.async_write_ha_state()
if next_update:
self._unsub_update = event_helper.async_track_point_in_time(
Expand Down
2 changes: 2 additions & 0 deletions custom_components/daily_schedule/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@
CONF_UTC: Final = "utc"

ATTR_NEXT_TOGGLE: Final = "next_toggle"
ATTR_NEXT_TOGGLES: Final = "next_toggles"
NEXT_TOGGLES_COUNT: Final = 4

SERVICE_SET: Final = "set"
12 changes: 12 additions & 0 deletions custom_components/daily_schedule/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,15 @@ def next_update(self, date: datetime.datetime) -> datetime.datetime | None:
return datetime.datetime.combine(
today + datetime.timedelta(days=1), timestamps[0], tzinfo=date.tzinfo
)

def next_updates(
self, date: datetime.datetime, count: int
) -> list[datetime.datetime]:
"""Get list of future updates."""
updates = []
if (update := self.next_update(date)) is not None:
while len(updates) < count:
updates.append(update)
if (update := self.next_update(update)) is None:
break
return updates
51 changes: 48 additions & 3 deletions tests/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from custom_components.daily_schedule.const import (
ATTR_NEXT_TOGGLE,
ATTR_NEXT_TOGGLES,
CONF_FROM,
CONF_SCHEDULE,
CONF_TO,
Expand Down Expand Up @@ -221,6 +222,12 @@ async def test_next_update(
state = hass.states.get(f"{Platform.BINARY_SENSOR}.test1")
assert state
assert state.attributes[ATTR_NEXT_TOGGLE] == in_5_minutes
assert state.attributes[ATTR_NEXT_TOGGLES] == [
in_5_minutes,
previous_5_minutes + datetime.timedelta(days=1),
in_5_minutes + datetime.timedelta(days=1),
previous_5_minutes + datetime.timedelta(days=2),
]

# After all ranges.
await setup_entity(
Expand All @@ -242,6 +249,12 @@ async def test_next_update(
state = hass.states.get(f"{Platform.BINARY_SENSOR}.test2")
assert state
assert state.attributes[ATTR_NEXT_TOGGLE] == expected_next_update
assert state.attributes[ATTR_NEXT_TOGGLES] == [
expected_next_update,
previous_5_minutes + datetime.timedelta(days=1),
expected_next_update + datetime.timedelta(days=1),
previous_5_minutes + datetime.timedelta(days=2),
]

# Before any range.
await setup_entity(
Expand All @@ -260,6 +273,12 @@ async def test_next_update(
next_update = async_track_point_in_time.call_args[0][2]
assert next_update == in_5_minutes
assert state.attributes[ATTR_NEXT_TOGGLE] == in_5_minutes
assert state.attributes[ATTR_NEXT_TOGGLES] == [
in_5_minutes,
in_10_minutes,
in_5_minutes + datetime.timedelta(days=1),
in_10_minutes + datetime.timedelta(days=1),
]
await async_cleanup(hass)


Expand Down Expand Up @@ -340,8 +359,8 @@ async def test_utc(
) -> None:
"""Test utc schedule."""
utc_time = datetime.datetime(2023, 5, 30, 12, tzinfo=pytz.utc) # 12pm
local_time = utc_time.astimezone(pytz.timezone("US/Eastern")) # 7am
offset = utc_time.timestamp() - local_time.replace(tzinfo=None).timestamp() # 5h
local_time = utc_time.astimezone(pytz.timezone("US/Pacific")) # 4am
offset = utc_time.timestamp() - local_time.replace(tzinfo=None).timestamp() # 8h
freezer.move_to(local_time)
entity_id = f"{Platform.BINARY_SENSOR}.my_test"
await setup_entity(
Expand All @@ -359,5 +378,31 @@ async def test_utc(
assert state
assert state.state == STATE_ON if utc else STATE_OFF
next_toggle_timestamp = state.attributes[ATTR_NEXT_TOGGLE].timestamp()
assert next_toggle_timestamp == utc_time.timestamp() + 1 if utc else offset
assert next_toggle_timestamp == local_time.timestamp() + (1 if utc else offset)
if utc:
assert state.attributes[ATTR_NEXT_TOGGLES][0].timestamp() == (
local_time.timestamp() + 1
)
assert state.attributes[ATTR_NEXT_TOGGLES][1].timestamp() == (
(local_time + datetime.timedelta(days=1)).timestamp()
)
assert state.attributes[ATTR_NEXT_TOGGLES][2].timestamp() == (
(local_time + datetime.timedelta(days=1)).timestamp() + 1
)
assert state.attributes[ATTR_NEXT_TOGGLES][3].timestamp() == (
(local_time + datetime.timedelta(days=2)).timestamp()
)
else:
assert state.attributes[ATTR_NEXT_TOGGLES][0].timestamp() == (
local_time.timestamp() + offset
)
assert state.attributes[ATTR_NEXT_TOGGLES][1].timestamp() == (
local_time.timestamp() + offset + 1
)
assert state.attributes[ATTR_NEXT_TOGGLES][2].timestamp() == (
(local_time + datetime.timedelta(days=1)).timestamp() + offset
)
assert state.attributes[ATTR_NEXT_TOGGLES][3].timestamp() == (
(local_time + datetime.timedelta(days=1)).timestamp() + offset + 1
)
await async_cleanup(hass)
23 changes: 23 additions & 0 deletions tests/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,26 @@ def test_next_update(
if next_update_sec_offset is not None
else None
)


def test_next_updates() -> None:
"""Test next updates."""
now = datetime.datetime.fromisoformat("2000-01-01")
assert Schedule(
[
{
CONF_FROM: "01:00",
CONF_TO: "02:00",
},
{
CONF_FROM: "03:00",
CONF_TO: "04:00",
},
]
).next_updates(now, 5) == [
now + datetime.timedelta(hours=1),
now + datetime.timedelta(hours=2),
now + datetime.timedelta(hours=3),
now + datetime.timedelta(hours=4),
now + datetime.timedelta(days=1, hours=1),
]

0 comments on commit 5d2553d

Please sign in to comment.