fix(binary_sensor): remove 6-hour lookahead limit for period icons

Simplified _has_future_periods() to check for ANY future periods instead
of limiting to 6-hour window. This ensures icons show 'waiting' state
whenever periods are scheduled, not just within artificial time limit.

Also added pragmatic fallback in timing calculator _find_next_period():
when skip_current=True but only one future period exists, return it
anyway instead of showing 'unknown'. This prevents timing sensors from
showing unknown during active periods.

Changes:
- binary_sensor/definitions.py: Removed PERIOD_LOOKAHEAD_HOURS constant
- binary_sensor/core.py: Simplified _has_future_periods() logic
- sensor/calculators/timing.py: Added pragmatic fallback for single period

Impact: Better user experience - icons always show future periods, timing
sensors show values even during edge cases.
This commit is contained in:
Julian Pawlowski 2025-11-22 13:04:17 +00:00
parent f373c01fbb
commit 2d0febdab3
3 changed files with 15 additions and 24 deletions

View file

@ -21,7 +21,6 @@ from .attributes import (
get_price_intervals_attributes,
get_tomorrow_data_available_attributes,
)
from .definitions import PERIOD_LOOKAHEAD_HOURS
if TYPE_CHECKING:
from collections.abc import Callable
@ -46,11 +45,6 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{entity_description.key}"
self._state_getter: Callable | None = self._get_value_getter()
self._time_sensitive_remove_listener: Callable | None = None
self._lifecycle_remove_listener: Callable | None = None
# Register for lifecycle push updates if this sensor depends on connection state
if entity_description.key in ("connection", "tomorrow_data_available"):
self._lifecycle_remove_listener = coordinator.register_lifecycle_callback(self.async_write_ha_state)
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
@ -71,11 +65,6 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
self._time_sensitive_remove_listener()
self._time_sensitive_remove_listener = None
# Remove lifecycle listener if registered
if self._lifecycle_remove_listener:
self._lifecycle_remove_listener()
self._lifecycle_remove_listener = None
@callback
def _handle_time_sensitive_update(self, time_service: TibberPricesTimeService) -> None:
"""
@ -270,10 +259,10 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
def _has_future_periods(self) -> bool:
"""
Check if there are periods starting within the next 6 hours.
Check if there are any future periods.
Returns True if any period starts between now and PERIOD_LOOKAHEAD_HOURS from now.
This provides a practical planning horizon instead of hard midnight cutoff.
Returns True if any period starts in the future (no time limit).
This ensures icons show "waiting" state whenever periods are scheduled.
"""
attrs = self._get_sensor_attributes()
if not attrs or "periods" not in attrs:
@ -282,15 +271,15 @@ class TibberPricesBinarySensor(TibberPricesEntity, BinarySensorEntity):
time = self.coordinator.time
periods = attrs.get("periods", [])
# Check if any period starts within the look-ahead window
# Check if any period starts in the future (no time limit)
for period in periods:
start_str = period.get("start")
if start_str:
# Already datetime object (periods come from coordinator.data)
start_time = start_str if not isinstance(start_str, str) else time.parse_datetime(start_str)
# Period starts in the future but within our horizon
if start_time and time.is_time_within_horizon(start_time, hours=PERIOD_LOOKAHEAD_HOURS):
# Period starts in the future
if start_time and time.is_in_future(start_time):
return True
return False

View file

@ -8,9 +8,8 @@ from homeassistant.components.binary_sensor import (
)
from homeassistant.const import EntityCategory
# Look-ahead window for future period detection (hours)
# Icons will show "waiting" state if a period starts within this window
PERIOD_LOOKAHEAD_HOURS = 6
# Period lookahead removed - icons show "waiting" state if ANY future periods exist
# No artificial time limit - show all periods until midnight
ENTITY_DESCRIPTIONS = (
BinarySensorEntityDescription(

View file

@ -158,7 +158,8 @@ class TibberPricesTimingCalculator(TibberPricesBaseCalculator):
Args:
periods: List of period dictionaries
skip_current: If True, skip the first future period (to get next-next)
skip_current: If True, try to skip the first future period (to get next-next)
If only one future period exists, return it anyway (pragmatic fallback)
Returns:
Next period dict or None if no future periods
@ -173,11 +174,13 @@ class TibberPricesTimingCalculator(TibberPricesBaseCalculator):
# Sort by start time to ensure correct order
future_periods.sort(key=lambda p: p["start"])
# Return second period if skip_current=True (next-next), otherwise first (next)
# If skip_current requested and we have multiple periods, return second
# If only one period left, return it anyway (pragmatic: better than showing unknown)
if skip_current and len(future_periods) > 1:
return future_periods[1]
if not skip_current and future_periods:
return future_periods[0]
# Default: return first future period
return future_periods[0] if future_periods else None
return None