mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-04-09 09:03:40 +00:00
feat(sensors): add trend_change_in_minutes countdown sensor
New duration sensor showing time until next price trend change as hours (e.g., 2.25 h). Registered in MINUTE_UPDATE_ENTITY_KEYS for per-minute updates. Shares cached attributes with next_price_trend_change timestamp sensor. Added trend attributes to _unrecorded_attributes (threshold/volatility/diff attributes excluded from recorder). Updated timer group size test expectation from 6 to 7. Impact: Users can display a live countdown to the next trend change on dashboards and use it in automations (e.g., "if < 0.25 h, prepare").
This commit is contained in:
parent
90e2c3c1dc
commit
91efeed90f
6 changed files with 39 additions and 2 deletions
|
|
@ -108,5 +108,7 @@ MINUTE_UPDATE_ENTITY_KEYS = frozenset(
|
|||
"peak_price_remaining_minutes",
|
||||
"peak_price_progress",
|
||||
"peak_price_next_in_minutes",
|
||||
# Trend change countdown sensor (needs minute updates)
|
||||
"trend_change_in_minutes",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,3 +37,6 @@ def _add_cached_trend_attributes(attributes: dict, key: str, cached_data: dict)
|
|||
# Add cached attributes (timestamp already set by platform)
|
||||
# State contains the timestamp of the trend change itself
|
||||
attributes.update(cached_data["trend_change_attributes"])
|
||||
elif key == "trend_change_in_minutes" and cached_data.get("trend_change_attributes"):
|
||||
# Duration sensor shares same cached attributes as the timestamp sensor
|
||||
attributes.update(cached_data["trend_change_attributes"])
|
||||
|
|
|
|||
|
|
@ -120,6 +120,22 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
|
|||
"cache_validity",
|
||||
"data_completeness",
|
||||
"data_status",
|
||||
"threshold_rising_%",
|
||||
"threshold_rising_strongly_%",
|
||||
"threshold_falling_%",
|
||||
"threshold_falling_strongly_%",
|
||||
"volatility_factor",
|
||||
"interval_count",
|
||||
"price_direction_since",
|
||||
"price_now",
|
||||
"trend_diff_%",
|
||||
# Dynamic keys for second_half diff (all trend hour variants)
|
||||
"second_half_3h_diff_from_current_%",
|
||||
"second_half_4h_diff_from_current_%",
|
||||
"second_half_5h_diff_from_current_%",
|
||||
"second_half_6h_diff_from_current_%",
|
||||
"second_half_8h_diff_from_current_%",
|
||||
"second_half_12h_diff_from_current_%",
|
||||
# Static/Rarely Changing
|
||||
"tomorrow_expected_after",
|
||||
"level_value",
|
||||
|
|
@ -313,7 +329,11 @@ class TibberPricesSensor(TibberPricesEntity, RestoreSensor):
|
|||
if self.entity_description.key.startswith("price_trend_"):
|
||||
self._trend_calculator.clear_trend_cache()
|
||||
# Clear trend calculation cache for trend sensors
|
||||
elif self.entity_description.key in ("current_price_trend", "next_price_trend_change"):
|
||||
elif self.entity_description.key in (
|
||||
"current_price_trend",
|
||||
"next_price_trend_change",
|
||||
"trend_change_in_minutes",
|
||||
):
|
||||
self._trend_calculator.clear_calculation_cache()
|
||||
|
||||
# For lifecycle sensor: Only write state if it actually changed (state-change filter)
|
||||
|
|
|
|||
|
|
@ -517,6 +517,17 @@ FUTURE_TREND_SENSORS = (
|
|||
state_class=None, # Timestamp: no statistics
|
||||
entity_registry_enabled_default=True,
|
||||
),
|
||||
# Trend change countdown sensor (how long until trend changes?)
|
||||
SensorEntityDescription(
|
||||
key="trend_change_in_minutes",
|
||||
translation_key="trend_change_in_minutes",
|
||||
icon="mdi:timer-outline",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.HOURS,
|
||||
state_class=None, # Countdown timer: no statistics
|
||||
suggested_display_precision=2,
|
||||
entity_registry_enabled_default=True,
|
||||
),
|
||||
# Price trend forecast sensors (will prices be higher/lower in X hours?)
|
||||
# Default enabled: 1h-5h
|
||||
SensorEntityDescription(
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ def get_value_getter_mapping( # noqa: PLR0913 - needs all calculators as parame
|
|||
# Current and next trend change sensors
|
||||
"current_price_trend": trend_calculator.get_current_trend_value,
|
||||
"next_price_trend_change": trend_calculator.get_next_trend_change_value,
|
||||
"trend_change_in_minutes": lambda: _minutes_to_hours(trend_calculator.get_trend_change_in_minutes_value()),
|
||||
# Price trend sensors
|
||||
"price_trend_1h": lambda: trend_calculator.get_price_trend_value(hours=1),
|
||||
"price_trend_2h": lambda: trend_calculator.get_price_trend_value(hours=2),
|
||||
|
|
|
|||
|
|
@ -448,7 +448,7 @@ def test_timer_group_sizes() -> None:
|
|||
"""
|
||||
# As of Nov 2025
|
||||
expected_time_sensitive_min = 40 # At least 40 sensors
|
||||
expected_minute_update = 6 # Exactly 6 timing sensors
|
||||
expected_minute_update = 7 # Exactly 7 timing sensors
|
||||
|
||||
assert len(TIME_SENSITIVE_ENTITY_KEYS) >= expected_time_sensitive_min, (
|
||||
f"Expected at least {expected_time_sensitive_min} TIME_SENSITIVE sensors, got {len(TIME_SENSITIVE_ENTITY_KEYS)}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue