mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-04-09 09:03:40 +00:00
Added `suggested_unit_of_measurement=UnitOfTime.HOURS` to all 7 DURATION sensors to prevent HA from auto-selecting minutes as the display unit. Without this, HA would pick "min" for small values (e.g., 0.75 h) and then display large values as "1238 Min." instead of the intended "20 Std. 38 Min." Affected sensors: - trend_change_in_minutes - best_price_period_duration / peak_price_period_duration - best_price_remaining_minutes / peak_price_remaining_minutes - best_price_next_in_minutes / peak_price_next_in_minutes BREAKING CHANGE: Sensor state unit changes from minutes to hours for users whose entity registry stored "min" as the display unit (the previous default). Automations using the raw state value (e.g., `state < 60` for "less than 60 minutes") must be updated to use hours (e.g., `state < 1`). The state attributes `remaining_minutes` and `next_in_minutes` continue to provide integer minutes and are unaffected. Impact: Duration sensors now display dynamically as "X h Y min" (e.g., "1 h 15 min") instead of a large minutes value like "1238 Min.". Users who manually customized the unit in HA settings are not affected.
977 lines
41 KiB
Python
977 lines
41 KiB
Python
"""
|
|
Sensor entity definitions for Tibber Prices.
|
|
|
|
This module contains all SensorEntityDescription definitions organized by
|
|
calculation method. Sensor definitions are declarative and independent of
|
|
the implementation logic.
|
|
|
|
Organization by calculation pattern:
|
|
1. Interval-based: Time offset from current interval
|
|
2. Rolling hour: 5-interval aggregation windows
|
|
3. Daily statistics: Calendar day min/max/avg
|
|
4. 24h windows: Trailing/leading statistics
|
|
5. Future forecast: N-hour windows from next interval
|
|
6. Volatility: Price variation analysis
|
|
7. Best/Peak Price timing: Period-based time tracking
|
|
8. Diagnostic: System metadata
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from homeassistant.components.sensor import (
|
|
SensorDeviceClass,
|
|
SensorEntityDescription,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.const import (
|
|
PERCENTAGE,
|
|
EntityCategory,
|
|
UnitOfArea,
|
|
UnitOfElectricCurrent,
|
|
UnitOfEnergy,
|
|
UnitOfTime,
|
|
)
|
|
|
|
# ============================================================================
|
|
# SENSOR DEFINITIONS - Grouped by calculation method
|
|
# ============================================================================
|
|
#
|
|
# Sensors are organized by HOW they calculate values, not WHAT they display.
|
|
# This groups sensors that share common logic and enables code reuse through
|
|
# unified handler methods.
|
|
#
|
|
# Calculation patterns:
|
|
# 1. Interval-based: Use time offset from current interval
|
|
# 2. Rolling hour: Aggregate 5-interval window (2 before + center + 2 after)
|
|
# 3. Daily statistics: Min/max/avg within calendar day boundaries
|
|
# 4. 24h windows: Trailing/leading from current interval
|
|
# 5. Future forecast: N-hour windows starting from next interval
|
|
# 6. Volatility: Statistical analysis of price variation
|
|
# 7. Best/Peak Price timing: Period-based time tracking (requires minute updates)
|
|
# 8. Diagnostic: System information and metadata
|
|
# ============================================================================
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 1. INTERVAL-BASED SENSORS (offset: -1, 0, +1 from current interval)
|
|
# ----------------------------------------------------------------------------
|
|
# All use find_price_data_for_interval() with time offset
|
|
# Shared handler: _get_interval_value(interval_offset, value_type)
|
|
|
|
INTERVAL_PRICE_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_interval_price",
|
|
translation_key="current_interval_price",
|
|
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=SensorStateClass.TOTAL, # MONETARY requires TOTAL or None
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="current_interval_price_base",
|
|
translation_key="current_interval_price_base",
|
|
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=SensorStateClass.TOTAL, # MONETARY requires TOTAL or None for Energy Dashboard
|
|
suggested_display_precision=4, # More precision for base currency (e.g., 0.2534 EUR/kWh)
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_interval_price",
|
|
translation_key="next_interval_price",
|
|
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on price level
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future value: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="previous_interval_price",
|
|
translation_key="previous_interval_price",
|
|
icon="mdi:cash-refund", # Static: arrow back indicates "past"
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Past snapshot: historical chart not useful
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_LEVEL_OPTIONS in const.py!
|
|
INTERVAL_LEVEL_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_interval_price_level",
|
|
translation_key="current_interval_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_interval_price_level",
|
|
translation_key="next_interval_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
SensorEntityDescription(
|
|
key="previous_interval_price_level",
|
|
translation_key="previous_interval_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on level value
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_RATING_OPTIONS in const.py!
|
|
INTERVAL_RATING_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_interval_price_rating",
|
|
translation_key="current_interval_price_rating",
|
|
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_interval_price_rating",
|
|
translation_key="next_interval_price_rating",
|
|
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
SensorEntityDescription(
|
|
key="previous_interval_price_rating",
|
|
translation_key="previous_interval_price_rating",
|
|
icon="mdi:thumbs-up-down", # Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on rating value
|
|
entity_registry_enabled_default=False,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 2. ROLLING HOUR SENSORS (5-interval window: 2 before + center + 2 after)
|
|
# ----------------------------------------------------------------------------
|
|
# All aggregate data from rolling 5-interval window around a specific hour
|
|
# Shared handler: _get_rolling_hour_value(hour_offset, value_type)
|
|
|
|
ROLLING_HOUR_PRICE_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_hour_average_price",
|
|
translation_key="current_hour_average_price",
|
|
icon="mdi:cash", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on aggregated price level
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling derived value: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_hour_average_price",
|
|
translation_key="next_hour_average_price",
|
|
icon="mdi:cash-fast", # Dynamic: shows cash-multiple/plus/cash/minus/remove based on aggregated price level
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future derived value: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_LEVEL_OPTIONS in const.py!
|
|
ROLLING_HOUR_LEVEL_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_hour_price_level",
|
|
translation_key="current_hour_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on aggregated level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_hour_price_level",
|
|
translation_key="next_hour_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on aggregated level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_RATING_OPTIONS in const.py!
|
|
ROLLING_HOUR_RATING_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="current_hour_price_rating",
|
|
translation_key="current_hour_price_rating",
|
|
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on aggregated rating value
|
|
icon="mdi:thumbs-up-down",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_hour_price_rating",
|
|
translation_key="next_hour_price_rating",
|
|
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on aggregated rating value
|
|
icon="mdi:thumbs-up-down",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 3. DAILY STATISTICS SENSORS (min/max/avg for calendar day boundaries)
|
|
# ----------------------------------------------------------------------------
|
|
# Calculate statistics for specific calendar days (today/tomorrow)
|
|
|
|
DAILY_STAT_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="lowest_price_today",
|
|
translation_key="lowest_price_today",
|
|
icon="mdi:arrow-collapse-down",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Daily snapshot: stays constant most of the day
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="highest_price_today",
|
|
translation_key="highest_price_today",
|
|
icon="mdi:arrow-collapse-up",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Daily snapshot: stays constant most of the day
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="average_price_today",
|
|
translation_key="average_price_today",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=SensorStateClass.TOTAL, # Keep TOTAL: useful to track daily avg over weeks/months
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="lowest_price_tomorrow",
|
|
translation_key="lowest_price_tomorrow",
|
|
icon="mdi:arrow-collapse-down",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future data: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="highest_price_tomorrow",
|
|
translation_key="highest_price_tomorrow",
|
|
icon="mdi:arrow-collapse-up",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future data: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="average_price_tomorrow",
|
|
translation_key="average_price_tomorrow",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future data: historical chart not useful
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_LEVEL_OPTIONS in const.py!
|
|
DAILY_LEVEL_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="yesterday_price_level",
|
|
translation_key="yesterday_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="today_price_level",
|
|
translation_key="today_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
SensorEntityDescription(
|
|
key="tomorrow_price_level",
|
|
translation_key="tomorrow_price_level",
|
|
icon="mdi:gauge", # Dynamic: shows gauge/gauge-empty/gauge-low/gauge-full based on daily level value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
|
|
),
|
|
)
|
|
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with PRICE_RATING_OPTIONS in const.py!
|
|
DAILY_RATING_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="yesterday_price_rating",
|
|
translation_key="yesterday_price_rating",
|
|
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
|
|
icon="mdi:thumbs-up-down",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="today_price_rating",
|
|
translation_key="today_price_rating",
|
|
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
|
|
icon="mdi:thumbs-up-down",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
SensorEntityDescription(
|
|
key="tomorrow_price_rating",
|
|
translation_key="tomorrow_price_rating",
|
|
# Dynamic: shows thumbs-up/thumbs-up-down/thumbs-down based on daily rating value
|
|
icon="mdi:thumbs-up-down",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "normal", "high"],
|
|
entity_registry_enabled_default=False, # Level is more commonly used
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 4. 24H WINDOW SENSORS (trailing/leading from current interval)
|
|
# ----------------------------------------------------------------------------
|
|
# Calculate statistics over sliding 24-hour windows
|
|
|
|
WINDOW_24H_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="trailing_price_average",
|
|
translation_key="trailing_price_average",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="leading_price_average",
|
|
translation_key="leading_price_average",
|
|
icon="mdi:chart-line-variant",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False, # Advanced use case
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="trailing_price_min",
|
|
translation_key="trailing_price_min",
|
|
icon="mdi:arrow-collapse-down",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="trailing_price_max",
|
|
translation_key="trailing_price_max",
|
|
icon="mdi:arrow-collapse-up",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="leading_price_min",
|
|
translation_key="leading_price_min",
|
|
icon="mdi:arrow-collapse-down",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False, # Advanced use case
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="leading_price_max",
|
|
translation_key="leading_price_max",
|
|
icon="mdi:arrow-collapse-up",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Rolling window: value shifts every 15min, history chart misleading
|
|
entity_registry_enabled_default=False, # Advanced use case
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 5. FUTURE FORECAST SENSORS (N-hour windows starting from next interval)
|
|
# ----------------------------------------------------------------------------
|
|
# Calculate averages and trends for upcoming time windows
|
|
|
|
FUTURE_MEAN_SENSORS = (
|
|
# Default enabled: 1h-5h
|
|
SensorEntityDescription(
|
|
key="next_avg_1h",
|
|
translation_key="next_avg_1h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_2h",
|
|
translation_key="next_avg_2h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_3h",
|
|
translation_key="next_avg_3h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_4h",
|
|
translation_key="next_avg_4h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_5h",
|
|
translation_key="next_avg_5h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
# Disabled by default: 6h, 8h, 12h (advanced use cases)
|
|
SensorEntityDescription(
|
|
key="next_avg_6h",
|
|
translation_key="next_avg_6h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_8h",
|
|
translation_key="next_avg_8h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_avg_12h",
|
|
translation_key="next_avg_12h",
|
|
icon="mdi:chart-line",
|
|
device_class=SensorDeviceClass.MONETARY,
|
|
state_class=None, # Future forecast: historical chart not useful
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
)
|
|
|
|
FUTURE_TREND_SENSORS = (
|
|
# Current trend sensor (what is the trend right now, valid until next change?)
|
|
SensorEntityDescription(
|
|
key="current_price_trend",
|
|
translation_key="current_price_trend",
|
|
icon="mdi:trending-up", # Dynamic: trending-up/trending-down/trending-neutral based on current trend
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
# Next trend change sensor (when will trend change?)
|
|
SensorEntityDescription(
|
|
key="next_price_trend_change",
|
|
translation_key="next_price_trend_change",
|
|
icon="mdi:clock-alert", # Dynamic: trending-up/trending-down/trending-neutral based on direction
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
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,
|
|
suggested_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(
|
|
key="price_trend_1h",
|
|
translation_key="price_trend_1h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_2h",
|
|
translation_key="price_trend_2h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_3h",
|
|
translation_key="price_trend_3h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_4h",
|
|
translation_key="price_trend_4h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_5h",
|
|
translation_key="price_trend_5h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=True,
|
|
),
|
|
# Disabled by default: 6h, 8h, 12h
|
|
SensorEntityDescription(
|
|
key="price_trend_6h",
|
|
translation_key="price_trend_6h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_8h",
|
|
translation_key="price_trend_8h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_trend_12h",
|
|
translation_key="price_trend_12h",
|
|
icon="mdi:trending-up", # Dynamic: shows trending-up/trending-down/trending-neutral based on trend value
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["strongly_falling", "falling", "stable", "rising", "strongly_rising"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 6. VOLATILITY SENSORS (coefficient of variation analysis)
|
|
# ----------------------------------------------------------------------------
|
|
# NOTE: Enum options are defined inline (not imported from const.py) to avoid
|
|
# import timing issues with Home Assistant's entity platform initialization.
|
|
# Keep in sync with VOLATILITY_OPTIONS in const.py!
|
|
|
|
VOLATILITY_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="today_volatility",
|
|
translation_key="today_volatility",
|
|
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
|
|
icon="mdi:chart-bell-curve-cumulative",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "moderate", "high", "very_high"],
|
|
),
|
|
SensorEntityDescription(
|
|
key="tomorrow_volatility",
|
|
translation_key="tomorrow_volatility",
|
|
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
|
|
icon="mdi:chart-bell-curve-cumulative",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "moderate", "high", "very_high"],
|
|
entity_registry_enabled_default=False, # Today's volatility is usually sufficient
|
|
),
|
|
SensorEntityDescription(
|
|
key="next_24h_volatility",
|
|
translation_key="next_24h_volatility",
|
|
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
|
|
icon="mdi:chart-bell-curve-cumulative",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "moderate", "high", "very_high"],
|
|
entity_registry_enabled_default=False, # Advanced use case
|
|
),
|
|
SensorEntityDescription(
|
|
key="today_tomorrow_volatility",
|
|
translation_key="today_tomorrow_volatility",
|
|
# Dynamic: shows chart-bell-curve/chart-gantt/finance based on volatility level
|
|
icon="mdi:chart-bell-curve-cumulative",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
state_class=None, # Enum values: no statistics
|
|
options=["low", "moderate", "high", "very_high"],
|
|
entity_registry_enabled_default=False, # Advanced use case
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# 7. BEST/PEAK PRICE TIMING SENSORS (period-based time tracking)
|
|
# ----------------------------------------------------------------------------
|
|
# These sensors track time relative to best_price/peak_price binary sensor periods.
|
|
# They require minute-by-minute updates via async_track_time_interval.
|
|
#
|
|
# When period is active (binary_sensor ON):
|
|
# - end_time: Timestamp when current period ends
|
|
# - remaining_minutes: Minutes until period ends
|
|
# - progress: Percentage of period completed (0-100%)
|
|
#
|
|
# When period is inactive (binary_sensor OFF):
|
|
# - next_start_time: Timestamp when next period starts
|
|
# - next_in_minutes: Minutes until next period starts
|
|
#
|
|
# All return None/Unknown when no period is active/scheduled.
|
|
|
|
BEST_PRICE_TIMING_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="best_price_end_time",
|
|
translation_key="best_price_end_time",
|
|
icon="mdi:clock-end",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
state_class=None, # Timestamps: no statistics
|
|
),
|
|
SensorEntityDescription(
|
|
key="best_price_period_duration",
|
|
translation_key="best_price_period_duration",
|
|
icon="mdi:timer",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Duration not needed in long-term statistics
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="best_price_remaining_minutes",
|
|
translation_key="best_price_remaining_minutes",
|
|
icon="mdi:timer-sand",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Countdown timers excluded from statistics
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="best_price_progress",
|
|
translation_key="best_price_progress",
|
|
icon="mdi:percent", # Dynamic: mdi:percent-0 to mdi:percent-100
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
state_class=None, # Progress counter: no statistics
|
|
suggested_display_precision=0,
|
|
),
|
|
SensorEntityDescription(
|
|
key="best_price_next_start_time",
|
|
translation_key="best_price_next_start_time",
|
|
icon="mdi:clock-start",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
state_class=None, # Timestamps: no statistics
|
|
),
|
|
SensorEntityDescription(
|
|
key="best_price_next_in_minutes",
|
|
translation_key="best_price_next_in_minutes",
|
|
icon="mdi:timer-outline",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Next-start timers excluded from statistics
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
PEAK_PRICE_TIMING_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="peak_price_end_time",
|
|
translation_key="peak_price_end_time",
|
|
icon="mdi:clock-end",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
state_class=None, # Timestamps: no statistics
|
|
),
|
|
SensorEntityDescription(
|
|
key="peak_price_period_duration",
|
|
translation_key="peak_price_period_duration",
|
|
icon="mdi:timer",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Duration not needed in long-term statistics
|
|
suggested_display_precision=2,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="peak_price_remaining_minutes",
|
|
translation_key="peak_price_remaining_minutes",
|
|
icon="mdi:timer-sand",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Countdown timers excluded from statistics
|
|
suggested_display_precision=2,
|
|
),
|
|
SensorEntityDescription(
|
|
key="peak_price_progress",
|
|
translation_key="peak_price_progress",
|
|
icon="mdi:percent", # Dynamic: mdi:percent-0 to mdi:percent-100
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
state_class=None, # Progress counter: no statistics
|
|
suggested_display_precision=0,
|
|
),
|
|
SensorEntityDescription(
|
|
key="peak_price_next_start_time",
|
|
translation_key="peak_price_next_start_time",
|
|
icon="mdi:clock-start",
|
|
device_class=SensorDeviceClass.TIMESTAMP,
|
|
state_class=None, # Timestamps: no statistics
|
|
),
|
|
SensorEntityDescription(
|
|
key="peak_price_next_in_minutes",
|
|
translation_key="peak_price_next_in_minutes",
|
|
icon="mdi:timer-outline",
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.HOURS,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
state_class=None, # Next-start timers excluded from statistics
|
|
suggested_display_precision=2,
|
|
),
|
|
)
|
|
|
|
# 8. DIAGNOSTIC SENSORS (data availability and metadata)
|
|
# ----------------------------------------------------------------------------
|
|
|
|
DIAGNOSTIC_SENSORS = (
|
|
SensorEntityDescription(
|
|
key="data_lifecycle_status",
|
|
translation_key="data_lifecycle_status",
|
|
icon="mdi:database-sync",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=["cached", "fresh", "refreshing", "searching_tomorrow", "turnover_pending", "error"],
|
|
state_class=None, # Status value: no statistics
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True, # Critical for debugging
|
|
),
|
|
# Home metadata from user data
|
|
SensorEntityDescription(
|
|
key="home_type",
|
|
translation_key="home_type",
|
|
icon="mdi:home-variant",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=["apartment", "rowhouse", "house", "cottage"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="home_size",
|
|
translation_key="home_size",
|
|
icon="mdi:ruler-square",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
|
|
state_class=None, # Static user metadata: no statistics useful
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=0,
|
|
),
|
|
SensorEntityDescription(
|
|
key="main_fuse_size",
|
|
translation_key="main_fuse_size",
|
|
icon="mdi:fuse",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
|
device_class=SensorDeviceClass.CURRENT,
|
|
state_class=None, # Static user metadata: no statistics useful
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=0,
|
|
),
|
|
SensorEntityDescription(
|
|
key="number_of_residents",
|
|
translation_key="number_of_residents",
|
|
icon="mdi:account-group",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
state_class=None, # Static user metadata: no statistics useful
|
|
entity_registry_enabled_default=False,
|
|
suggested_display_precision=0,
|
|
),
|
|
SensorEntityDescription(
|
|
key="primary_heating_source",
|
|
translation_key="primary_heating_source",
|
|
icon="mdi:heating-coil",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=[
|
|
"air2air_heatpump",
|
|
"air2water_heatpump",
|
|
"boiler",
|
|
"central_heating",
|
|
"district_heating",
|
|
"district_heating",
|
|
"district",
|
|
"electric_boiler",
|
|
"electricity",
|
|
"floor",
|
|
"gas",
|
|
"ground_heatpump",
|
|
"ground",
|
|
"oil",
|
|
"other",
|
|
"waste",
|
|
],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
# Metering point data
|
|
SensorEntityDescription(
|
|
key="grid_company",
|
|
translation_key="grid_company",
|
|
icon="mdi:transmission-tower",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
SensorEntityDescription(
|
|
key="grid_area_code",
|
|
translation_key="grid_area_code",
|
|
icon="mdi:map-marker",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="price_area_code",
|
|
translation_key="price_area_code",
|
|
icon="mdi:currency-eur",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="consumption_ean",
|
|
translation_key="consumption_ean",
|
|
icon="mdi:barcode",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="production_ean",
|
|
translation_key="production_ean",
|
|
icon="mdi:solar-power",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="energy_tax_type",
|
|
translation_key="energy_tax_type",
|
|
icon="mdi:cash",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="vat_type",
|
|
translation_key="vat_type",
|
|
icon="mdi:percent",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="estimated_annual_consumption",
|
|
translation_key="estimated_annual_consumption",
|
|
icon="mdi:lightning-bolt",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
|
device_class=SensorDeviceClass.ENERGY,
|
|
state_class=None, # Static Tibber estimate, not an actual accumulating counter
|
|
suggested_display_precision=0,
|
|
),
|
|
# Subscription data
|
|
SensorEntityDescription(
|
|
key="subscription_status",
|
|
translation_key="subscription_status",
|
|
icon="mdi:file-document-check",
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=["running", "ended", "pending", "unknown"],
|
|
entity_registry_enabled_default=False,
|
|
),
|
|
SensorEntityDescription(
|
|
key="chart_data_export",
|
|
translation_key="chart_data_export",
|
|
icon="mdi:database-export",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=["pending", "ready", "error"],
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=False, # Opt-in
|
|
),
|
|
SensorEntityDescription(
|
|
key="chart_metadata",
|
|
translation_key="chart_metadata",
|
|
icon="mdi:chart-box-outline",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=["pending", "ready", "error"],
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
entity_registry_enabled_default=True, # Critical for chart features
|
|
),
|
|
)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# COMBINED SENSOR DEFINITIONS
|
|
# ----------------------------------------------------------------------------
|
|
|
|
ENTITY_DESCRIPTIONS = (
|
|
*INTERVAL_PRICE_SENSORS,
|
|
*INTERVAL_LEVEL_SENSORS,
|
|
*INTERVAL_RATING_SENSORS,
|
|
*ROLLING_HOUR_PRICE_SENSORS,
|
|
*ROLLING_HOUR_LEVEL_SENSORS,
|
|
*ROLLING_HOUR_RATING_SENSORS,
|
|
*DAILY_STAT_SENSORS,
|
|
*DAILY_LEVEL_SENSORS,
|
|
*DAILY_RATING_SENSORS,
|
|
*WINDOW_24H_SENSORS,
|
|
*FUTURE_MEAN_SENSORS,
|
|
*FUTURE_TREND_SENSORS,
|
|
*VOLATILITY_SENSORS,
|
|
*BEST_PRICE_TIMING_SENSORS,
|
|
*PEAK_PRICE_TIMING_SENSORS,
|
|
*DIAGNOSTIC_SENSORS,
|
|
)
|