hass.tibber_prices/custom_components/tibber_prices/sensor/definitions.py

564 lines
20 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. Diagnostic: System metadata
"""
from __future__ import annotations
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
)
from homeassistant.const import EntityCategory
# ============================================================================
# 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. 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",
name="Current Electricity Price",
icon="mdi:cash", # Dynamic: will show cash-multiple/plus/cash/minus/remove based on level
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=2,
),
SensorEntityDescription(
key="next_interval_price",
translation_key="next_interval_price",
name="Next Price",
icon="mdi:cash-fast", # Static: motion lines indicate "coming soon"
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=2,
),
SensorEntityDescription(
key="previous_interval_price",
translation_key="previous_interval_price",
name="Previous Electricity Price",
icon="mdi:cash-refund", # Static: arrow back indicates "past"
device_class=SensorDeviceClass.MONETARY,
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",
name="Current Price Level",
icon="mdi:gauge",
device_class=SensorDeviceClass.ENUM,
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="next_interval_price_level",
translation_key="next_interval_price_level",
name="Next Price Level",
icon="mdi:gauge-empty",
device_class=SensorDeviceClass.ENUM,
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="previous_interval_price_level",
translation_key="previous_interval_price_level",
name="Previous Price Level",
icon="mdi:gauge-empty",
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.ENUM,
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",
name="Current Price Rating",
icon="mdi:star-outline",
device_class=SensorDeviceClass.ENUM,
options=["low", "normal", "high"],
),
SensorEntityDescription(
key="next_interval_price_rating",
translation_key="next_interval_price_rating",
name="Next Price Rating",
icon="mdi:star-half-full",
device_class=SensorDeviceClass.ENUM,
options=["low", "normal", "high"],
),
SensorEntityDescription(
key="previous_interval_price_rating",
translation_key="previous_interval_price_rating",
name="Previous Price Rating",
icon="mdi:star-half-full",
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.ENUM,
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",
translation_key="current_hour_average",
name="Current Hour Average Price",
icon="mdi:cash", # Dynamic: will show cash-multiple/plus/cash/minus/remove based on level
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="next_hour_average",
translation_key="next_hour_average",
name="Next Hour Average Price",
icon="mdi:clock-fast", # Static: clock indicates "next time period"
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
)
# 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",
name="Current Hour Price Level",
icon="mdi:gauge",
device_class=SensorDeviceClass.ENUM,
options=["very_cheap", "cheap", "normal", "expensive", "very_expensive"],
),
SensorEntityDescription(
key="next_hour_price_level",
translation_key="next_hour_price_level",
name="Next Hour Price Level",
icon="mdi:gauge-empty",
device_class=SensorDeviceClass.ENUM,
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",
name="Current Hour Price Rating",
icon="mdi:star-outline",
device_class=SensorDeviceClass.ENUM,
options=["low", "normal", "high"],
),
SensorEntityDescription(
key="next_hour_price_rating",
translation_key="next_hour_price_rating",
name="Next Hour Price Rating",
icon="mdi:star-half-full",
device_class=SensorDeviceClass.ENUM,
options=["low", "normal", "high"],
),
)
# ----------------------------------------------------------------------------
# 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",
name="Today's Lowest Price",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="highest_price_today",
translation_key="highest_price_today",
name="Today's Highest Price",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="average_price_today",
translation_key="average_price_today",
name="Today's Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="lowest_price_tomorrow",
translation_key="lowest_price_tomorrow",
name="Tomorrow's Lowest Price",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="highest_price_tomorrow",
translation_key="highest_price_tomorrow",
name="Tomorrow's Highest Price",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="average_price_tomorrow",
translation_key="average_price_tomorrow",
name="Tomorrow's Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
)
# ----------------------------------------------------------------------------
# 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",
name="Trailing 24h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
entity_registry_enabled_default=False,
suggested_display_precision=1,
),
SensorEntityDescription(
key="leading_price_average",
translation_key="leading_price_average",
name="Leading 24h Average Price",
icon="mdi:chart-line-variant",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="trailing_price_min",
translation_key="trailing_price_min",
name="Trailing 24h Minimum Price",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
entity_registry_enabled_default=False,
suggested_display_precision=1,
),
SensorEntityDescription(
key="trailing_price_max",
translation_key="trailing_price_max",
name="Trailing 24h Maximum Price",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
entity_registry_enabled_default=False,
suggested_display_precision=1,
),
SensorEntityDescription(
key="leading_price_min",
translation_key="leading_price_min",
name="Leading 24h Minimum Price",
icon="mdi:arrow-collapse-down",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
SensorEntityDescription(
key="leading_price_max",
translation_key="leading_price_max",
name="Leading 24h Maximum Price",
icon="mdi:arrow-collapse-up",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
),
)
# ----------------------------------------------------------------------------
# 5. FUTURE FORECAST SENSORS (N-hour windows starting from next interval)
# ----------------------------------------------------------------------------
# Calculate averages and trends for upcoming time windows
FUTURE_AVG_SENSORS = (
# Default enabled: 1h-5h
SensorEntityDescription(
key="next_avg_1h",
translation_key="next_avg_1h",
name="Next 1h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_2h",
translation_key="next_avg_2h",
name="Next 2h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_3h",
translation_key="next_avg_3h",
name="Next 3h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_4h",
translation_key="next_avg_4h",
name="Next 4h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="next_avg_5h",
translation_key="next_avg_5h",
name="Next 5h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h (advanced use cases)
SensorEntityDescription(
key="next_avg_6h",
translation_key="next_avg_6h",
name="Next 6h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_avg_8h",
translation_key="next_avg_8h",
name="Next 8h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="next_avg_12h",
translation_key="next_avg_12h",
name="Next 12h Average Price",
icon="mdi:chart-line",
device_class=SensorDeviceClass.MONETARY,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
)
FUTURE_TREND_SENSORS = (
# Default enabled: 1h-5h
SensorEntityDescription(
key="price_trend_1h",
translation_key="price_trend_1h",
name="Price Trend (1h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trend_2h",
translation_key="price_trend_2h",
name="Price Trend (2h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trend_3h",
translation_key="price_trend_3h",
name="Price Trend (3h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trend_4h",
translation_key="price_trend_4h",
name="Price Trend (4h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=True,
),
SensorEntityDescription(
key="price_trend_5h",
translation_key="price_trend_5h",
name="Price Trend (5h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=True,
),
# Disabled by default: 6h, 8h, 12h
SensorEntityDescription(
key="price_trend_6h",
translation_key="price_trend_6h",
name="Price Trend (6h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trend_8h",
translation_key="price_trend_8h",
name="Price Trend (8h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="price_trend_12h",
translation_key="price_trend_12h",
name="Price Trend (12h)",
icon="mdi:trending-up",
device_class=SensorDeviceClass.ENUM,
options=["rising", "falling", "stable"],
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",
name="Today's Price Volatility",
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
options=["low", "moderate", "high", "very_high"],
),
SensorEntityDescription(
key="tomorrow_volatility",
translation_key="tomorrow_volatility",
name="Tomorrow's Price Volatility",
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
options=["low", "moderate", "high", "very_high"],
),
SensorEntityDescription(
key="next_24h_volatility",
translation_key="next_24h_volatility",
name="Next 24h Price Volatility",
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
options=["low", "moderate", "high", "very_high"],
),
SensorEntityDescription(
key="today_tomorrow_volatility",
translation_key="today_tomorrow_volatility",
name="Today + Tomorrow Price Volatility",
icon="mdi:chart-bell-curve-cumulative",
device_class=SensorDeviceClass.ENUM,
options=["low", "moderate", "high", "very_high"],
),
)
# ----------------------------------------------------------------------------
# 7. DIAGNOSTIC SENSORS (data availability and metadata)
# ----------------------------------------------------------------------------
DIAGNOSTIC_SENSORS = (
SensorEntityDescription(
key="data_timestamp",
translation_key="data_timestamp",
name="Data Expiration",
icon="mdi:clock-check",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key="price_forecast",
translation_key="price_forecast",
name="Price Forecast",
icon="mdi:chart-line",
entity_category=EntityCategory.DIAGNOSTIC,
),
)
# ----------------------------------------------------------------------------
# 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,
*WINDOW_24H_SENSORS,
*FUTURE_AVG_SENSORS,
*FUTURE_TREND_SENSORS,
*VOLATILITY_SENSORS,
*DIAGNOSTIC_SENSORS,
)