feat(docs): add multi-language entity reference with search

Add a comprehensive entity reference system that helps users find
entities across all 5 supported languages (EN, DE, NO, NL, SV).

Core components:
- Generator script (scripts/docs/generate-sensor-reference) that
  builds sensor-reference.md from translation files with --check
  mode for CI validation
- EntityRef component for compact inline entity annotations with
  tooltip and version-aware linking to the reference table
- EntitySearch component with live filtering, clickable results,
  keyboard navigation, "/" shortcut to focus, category filter chips,
  match highlighting, copy-entity-ID button per row, back-links to
  documentation pages, persistent row highlights, hash-based deep
  linking, and mobile-responsive layout
- MDXComponents theme override for global component registration

Documentation updates:
- New sensor-reference.md page (115 entities x 5 languages)
- EntityRef annotations across 10 documentation pages
- Sidebar entry for quick navigation
- CI integration (docusaurus.yml + scripts/check)
- Ruff per-file-ignores for scripts/ (T201, INP001)

Impact: Users can now find any entity by its localized display name
regardless of their UI language. Inline EntityRef annotations link
directly to the multi-language lookup table with version-aware URLs.
This commit is contained in:
Julian Pawlowski 2026-04-11 09:55:28 +00:00
parent cd59834277
commit 0e699ae142
20 changed files with 2024 additions and 120 deletions

View file

@ -52,6 +52,15 @@ jobs:
docs/user/package-lock.json docs/user/package-lock.json
docs/developer/package-lock.json docs/developer/package-lock.json
# VERIFY GENERATED DOCS
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.14'
- name: Verify sensor reference is up-to-date
run: python3 scripts/docs/generate-sensor-reference --check
# USER DOCS BUILD # USER DOCS BUILD
- name: Install user docs dependencies - name: Install user docs dependencies
working-directory: docs/user working-directory: docs/user

View file

@ -20,7 +20,7 @@ When you write YAML directly (automations, scripts, Lovelace dashboard cards), y
2. Find the **Tibber Prices** integration card 2. Find the **Tibber Prices** integration card
3. Click the **⋮** (three-dot) menu on the card 3. Click the **⋮** (three-dot) menu on the card
4. Choose **"Copy Config Entry ID"** 4. Choose **"Copy Config Entry ID"**
5. Paste the value wherever you see `YOUR_ENTRY_ID` in the YAML examples 5. Paste the value wherever you see `YOUR_CONFIG_ENTRY_ID` in the YAML examples
The ID looks like a long alphanumeric string, for example `01JKPC7AB3EF4GH5IJ6KL7MN8P`. The ID looks like a long alphanumeric string, for example `01JKPC7AB3EF4GH5IJ6KL7MN8P`.
@ -30,8 +30,6 @@ If you have configured more than one Tibber home, each home has its own entry ID
## Available Actions ## Available Actions
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**).
### tibber_prices.get_chartdata ### tibber_prices.get_chartdata
**Purpose:** Returns electricity price data in chart-friendly formats for visualization and analysis. **Purpose:** Returns electricity price data in chart-friendly formats for visualization and analysis.
@ -51,7 +49,7 @@ If you have configured more than one Tibber home, each home has its own entry ID
```yaml ```yaml
service: tibber_prices.get_chartdata service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: ["today", "tomorrow"] day: ["today", "tomorrow"]
output_format: array_of_objects output_format: array_of_objects
response_variable: chart_data response_variable: chart_data
@ -92,7 +90,7 @@ Omit the `day` parameter to get a dynamic 48-hour rolling window that automatica
```yaml ```yaml
service: tibber_prices.get_chartdata service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
# Omit 'day' for rolling window # Omit 'day' for rolling window
output_format: array_of_objects output_format: array_of_objects
response_variable: chart_data response_variable: chart_data
@ -111,7 +109,7 @@ Get best price periods as summaries instead of intervals:
```yaml ```yaml
service: tibber_prices.get_chartdata service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
period_filter: best_price # or peak_price period_filter: best_price # or peak_price
day: ["today", "tomorrow"] day: ["today", "tomorrow"]
include_level: true include_level: true
@ -124,7 +122,7 @@ response_variable: periods
```yaml ```yaml
service: tibber_prices.get_chartdata service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
level_filter: ["VERY_CHEAP", "CHEAP"] # Only cheap periods level_filter: ["VERY_CHEAP", "CHEAP"] # Only cheap periods
rating_level_filter: ["LOW"] # Only low-rated prices rating_level_filter: ["LOW"] # Only low-rated prices
insert_nulls: segments # Add nulls at segment boundaries insert_nulls: segments # Add nulls at segment boundaries
@ -150,7 +148,7 @@ You can include the raw energy price (spot price) and/or tax component in chart
```yaml ```yaml
service: tibber_prices.get_chartdata service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: ["today", "tomorrow"] day: ["today", "tomorrow"]
include_energy: true include_energy: true
include_tax: true include_tax: true
@ -201,7 +199,7 @@ Returns data points like:
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: today # Optional: yesterday, today, tomorrow, rolling_window, rolling_window_autozoom day: today # Optional: yesterday, today, tomorrow, rolling_window, rolling_window_autozoom
level_type: rating_level # or "level" for 5-level classification level_type: rating_level # or "level" for 5-level classification
highlight_best_price: true # Show best price period overlays highlight_best_price: true # Show best price period overlays
@ -230,7 +228,7 @@ Rolling window configurations automatically integrate with the `chart_metadata`
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: today day: today
level_type: rating_level level_type: rating_level
response_variable: config response_variable: config
@ -245,7 +243,7 @@ type: custom:apexcharts-card
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
# Omit 'day' for rolling window (or use 'rolling_window') # Omit 'day' for rolling window (or use 'rolling_window')
level_type: level # 5-level classification level_type: level # 5-level classification
highlight_best_price: true highlight_best_price: true
@ -298,7 +296,7 @@ Use the response in Lovelace dashboards by copying the generated YAML.
```yaml ```yaml
service: tibber_prices.refresh_user_data service: tibber_prices.refresh_user_data
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
``` ```
**Note:** User data is cached for 24 hours. Trigger this action only when you need immediate updates (e.g., after changing Tibber subscriptions). **Note:** User data is cached for 24 hours. Trigger this action only when you need immediate updates (e.g., after changing Tibber subscriptions).

View file

@ -19,7 +19,7 @@
> >
> These examples provide a good starting point but must be tailored to your individual Home Assistant setup. > These examples provide a good starting point but must be tailored to your individual Home Assistant setup.
> >
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## Price-Based Automations ## Price-Based Automations
@ -277,10 +277,10 @@ A common misconception: **"rising" does NOT mean "too late"**. It means your cur
| What You Want | Sensors to Combine | | What You Want | Sensors to Combine |
|---|---| |---|---|
| **"Is it cheap right now?"** | `rating_level` attribute (VERY_CHEAP, CHEAP) | | **"Is it cheap right now?"** | `rating_level` attribute (VERY_CHEAP, CHEAP) |
| **"Will prices go up or down?"** | `current_price_trend` state (falling/stable/rising) | | **"Will prices go up or down?"** | <EntityRef id="current_price_trend" noStrong>`current_price_trend`</EntityRef> state |
| **"When will the trend change?"** | `next_price_trend_change` state (timestamp) | | **"When will the trend change?"** | <EntityRef id="next_price_trend_change" noStrong>`next_price_trend_change`</EntityRef> state |
| **"How cheap will it get?"** | `next_Nh_avg` attribute on trend sensors | | **"How cheap will it get?"** | `next_Nh_avg` attribute on trend sensors |
| **"Is the price drop meaningful?"** | `today_s_price_volatility` (not low = meaningful) | | **"Is the price drop meaningful?"** | <EntityRef id="today_volatility" noStrong>`today_s_price_volatility`</EntityRef> |
| **"Ride the full cheap wave"** | `rating_level` + `current_price_trend` + `best_price_period` | | **"Ride the full cheap wave"** | `rating_level` + `current_price_trend` + `best_price_period` |
--- ---
@ -513,11 +513,11 @@ automation:
The `tibber_prices.get_apexcharts_yaml` service generates basic ApexCharts card configuration examples for visualizing electricity prices. The `tibber_prices.get_apexcharts_yaml` service generates basic ApexCharts card configuration examples for visualizing electricity prices.
:::info Finding your Entry ID (`entry_id`) :::info Finding your Entry ID (`entry_id`)
The examples below contain `entry_id: YOUR_ENTRY_ID`. This value identifies which Tibber home (integration instance) the action targets. The examples below contain `entry_id: YOUR_CONFIG_ENTRY_ID`. This value identifies which Tibber home (integration instance) the action targets.
**In the Action UI (Developer Tools → Actions or the automation editor):** The `entry_id` field is a **dropdown** — just select your Tibber home and HA fills in the correct ID automatically. **In the Action UI (Developer Tools → Actions or the automation editor):** The `entry_id` field is a **dropdown** — just select your Tibber home and HA fills in the correct ID automatically.
**In YAML:** Go to **Settings → Devices & Services**, find the **Tibber Prices** card, open the **⋮** (three-dot) menu, and choose **"Copy Config Entry ID"**. Paste the copied value in place of `YOUR_ENTRY_ID`. **In YAML:** Go to **Settings → Devices & Services**, find the **Tibber Prices** card, open the **⋮** (three-dot) menu, and choose **"Copy Config Entry ID"**. Paste the copied value in place of `YOUR_CONFIG_ENTRY_ID`.
::: :::
### Prerequisites ### Prerequisites
@ -542,7 +542,7 @@ The examples below contain `entry_id: YOUR_ENTRY_ID`. This value identifies whic
# Generate configuration via automation/script # Generate configuration via automation/script
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: today # or "yesterday", "tomorrow" day: today # or "yesterday", "tomorrow"
level_type: rating_level # or "level" for 5-level view level_type: rating_level # or "level" for 5-level view
response_variable: apexcharts_config response_variable: apexcharts_config
@ -557,7 +557,7 @@ For a dynamic chart that automatically adapts to data availability:
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: rolling_window # Or omit for same behavior (default) day: rolling_window # Or omit for same behavior (default)
level_type: rating_level level_type: rating_level
response_variable: apexcharts_config response_variable: apexcharts_config
@ -576,7 +576,7 @@ For progressive zoom-in throughout the day:
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: rolling_window_autozoom day: rolling_window_autozoom
level_type: rating_level level_type: rating_level
response_variable: apexcharts_config response_variable: apexcharts_config

View file

@ -4,14 +4,14 @@ This guide showcases the different chart configurations available through the `t
> **Quick Start:** Call the action with your desired parameters, copy the generated YAML, and paste it into your Lovelace dashboard! > **Quick Start:** Call the action with your desired parameters, copy the generated YAML, and paste it into your Lovelace dashboard!
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
:::info Finding your Entry ID (`entry_id`) :::info Finding your Entry ID (`entry_id`)
Every example below contains `entry_id: YOUR_ENTRY_ID`. This value identifies which Tibber home (integration instance) the action targets. Every example below contains `entry_id: YOUR_CONFIG_ENTRY_ID`. This value identifies which Tibber home (integration instance) the action targets.
**In the Action UI (Developer Tools → Actions or the automation editor):** The `entry_id` field is a **dropdown** — just select your Tibber home and HA fills in the correct ID automatically. **In the Action UI (Developer Tools → Actions or the automation editor):** The `entry_id` field is a **dropdown** — just select your Tibber home and HA fills in the correct ID automatically.
**In YAML:** Go to **Settings → Devices & Services**, find the **Tibber Prices** card, open the **⋮** (three-dot) menu, and choose **"Copy Config Entry ID"**. Paste the copied value in place of `YOUR_ENTRY_ID`. **In YAML:** Go to **Settings → Devices & Services**, find the **Tibber Prices** card, open the **⋮** (three-dot) menu, and choose **"Copy Config Entry ID"**. Paste the copied value in place of `YOUR_CONFIG_ENTRY_ID`.
::: :::
## Overview ## Overview
@ -42,7 +42,7 @@ The integration can generate 4 different chart modes, each optimized for specifi
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: today day: today
level_type: rating_level level_type: rating_level
highlight_best_price: true highlight_best_price: true
@ -72,7 +72,7 @@ data:
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
# Omit 'day' for rolling window # Omit 'day' for rolling window
level_type: rating_level level_type: rating_level
highlight_best_price: true highlight_best_price: true
@ -106,7 +106,7 @@ data:
```yaml ```yaml
service: tibber_prices.get_apexcharts_yaml service: tibber_prices.get_apexcharts_yaml
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: rolling_window_autozoom day: rolling_window_autozoom
level_type: rating_level level_type: rating_level
highlight_best_price: true highlight_best_price: true

View file

@ -1,6 +1,6 @@
# Configuration # Configuration
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## Initial Setup ## Initial Setup
@ -214,25 +214,25 @@ When enabled, these entities override the corresponding Options Flow settings:
| Entity | Type | Range | Description | | Entity | Type | Range | Description |
|--------|------|-------|-------------| |--------|------|-------|-------------|
| **Best Price: Flexibility** | Number | 0-50% | Maximum above daily minimum for "best price" intervals | | <EntityRef id="best_price_flex_override">Best Price: Flexibility</EntityRef> | Number | 0-50% | Maximum above daily minimum for "best price" intervals |
| **Best Price: Minimum Distance** | Number | -50-0% | Required distance below daily average | | <EntityRef id="best_price_min_distance_override">Best Price: Minimum Distance</EntityRef> | Number | -50-0% | Required distance below daily average |
| **Best Price: Minimum Period Length** | Number | 15-180 min | Shortest period duration to consider | | <EntityRef id="best_price_min_period_length_override">Best Price: Minimum Period Length</EntityRef> | Number | 15-180 min | Shortest period duration to consider |
| **Best Price: Minimum Periods** | Number | 1-10 | Target number of periods per day | | <EntityRef id="best_price_min_periods_override">Best Price: Minimum Periods</EntityRef> | Number | 1-10 | Target number of periods per day |
| **Best Price: Relaxation Attempts** | Number | 1-12 | Steps to try when relaxing criteria | | <EntityRef id="best_price_relaxation_attempts_override">Best Price: Relaxation Attempts</EntityRef> | Number | 1-12 | Steps to try when relaxing criteria |
| **Best Price: Gap Tolerance** | Number | 0-8 | Consecutive intervals allowed above threshold | | <EntityRef id="best_price_gap_tolerance_override">Best Price: Gap Tolerance</EntityRef> | Number | 0-8 | Consecutive intervals allowed above threshold |
| **Best Price: Achieve Minimum Count** | Switch | On/Off | Enable relaxation algorithm | | <EntityRef id="best_price_achieve_min_count_override">Best Price: Achieve Minimum Count</EntityRef> | Switch | On/Off | Enable relaxation algorithm |
#### Peak Price Period Settings #### Peak Price Period Settings
| Entity | Type | Range | Description | | Entity | Type | Range | Description |
|--------|------|-------|-------------| |--------|------|-------|-------------|
| **Peak Price: Flexibility** | Number | -50-0% | Maximum below daily maximum for "peak price" intervals | | <EntityRef id="peak_price_flex_override">Peak Price: Flexibility</EntityRef> | Number | -50-0% | Maximum below daily maximum for "peak price" intervals |
| **Peak Price: Minimum Distance** | Number | 0-50% | Required distance above daily average | | <EntityRef id="peak_price_min_distance_override">Peak Price: Minimum Distance</EntityRef> | Number | 0-50% | Required distance above daily average |
| **Peak Price: Minimum Period Length** | Number | 15-180 min | Shortest period duration to consider | | <EntityRef id="peak_price_min_period_length_override">Peak Price: Minimum Period Length</EntityRef> | Number | 15-180 min | Shortest period duration to consider |
| **Peak Price: Minimum Periods** | Number | 1-10 | Target number of periods per day | | <EntityRef id="peak_price_min_periods_override">Peak Price: Minimum Periods</EntityRef> | Number | 1-10 | Target number of periods per day |
| **Peak Price: Relaxation Attempts** | Number | 1-12 | Steps to try when relaxing criteria | | <EntityRef id="peak_price_relaxation_attempts_override">Peak Price: Relaxation Attempts</EntityRef> | Number | 1-12 | Steps to try when relaxing criteria |
| **Peak Price: Gap Tolerance** | Number | 0-8 | Consecutive intervals allowed below threshold | | <EntityRef id="peak_price_gap_tolerance_override">Peak Price: Gap Tolerance</EntityRef> | Number | 0-8 | Consecutive intervals allowed below threshold |
| **Peak Price: Achieve Minimum Count** | Switch | On/Off | Enable relaxation algorithm | | <EntityRef id="peak_price_achieve_min_count_override">Peak Price: Achieve Minimum Count</EntityRef> | Switch | On/Off | Enable relaxation algorithm |
### How Runtime Overrides Work ### How Runtime Overrides Work

View file

@ -2,7 +2,7 @@
Beautiful dashboard layouts using Tibber Prices sensors. Beautiful dashboard layouts using Tibber Prices sensors.
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## Basic Price Display Card ## Basic Price Display Card

View file

@ -2,7 +2,7 @@
Many sensors in the Tibber Prices integration automatically change their icon based on their current state. This provides instant visual feedback about price levels, trends, and periods without needing to read the actual values. Many sensors in the Tibber Prices integration automatically change their icon based on their current state. This provides instant visual feedback about price levels, trends, and periods without needing to read the actual values.
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## What are Dynamic Icons? ## What are Dynamic Icons?
@ -26,9 +26,9 @@ To see which icon a sensor currently uses:
**Common sensor types with dynamic icons:** **Common sensor types with dynamic icons:**
- Price level sensors (e.g., `current_price_level`) - Price level sensors (e.g., `current_price_level``current_interval_price_level`)
- Price rating sensors (e.g., `current_price_rating`) - Price rating sensors (e.g., `current_price_rating``current_interval_price_rating`)
- Volatility sensors (e.g., `today_s_price_volatility`) - Volatility sensors (e.g., `today_s_price_volatility``today_volatility`)
- Binary sensors (e.g., `best_price_period`, `peak_price_period`) - Binary sensors (e.g., `best_price_period`, `peak_price_period`)
## Using Dynamic Icons in Your Dashboard ## Using Dynamic Icons in Your Dashboard

View file

@ -112,7 +112,7 @@ If you see unexpected units, check your configuration in the integration options
## Automation Questions ## Automation Questions
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
### How do I run dishwasher during cheap period? ### How do I run dishwasher during cheap period?

View file

@ -10,7 +10,7 @@ Many sensors in the Tibber Prices integration provide an `icon_color` attribute
> **Related:** Many sensors also automatically change their **icon** based on state. See the **[Dynamic Icons Guide](dynamic-icons.md)** for details. > **Related:** Many sensors also automatically change their **icon** based on state. See the **[Dynamic Icons Guide](dynamic-icons.md)** for details.
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## What is icon_color? ## What is icon_color?
@ -40,12 +40,12 @@ Many sensors provide the `icon_color` attribute for dynamic styling. To see if a
**Common sensor types with icon_color:** **Common sensor types with icon_color:**
- Price level sensors (e.g., `current_price_level`) - Price level sensors (e.g., `current_price_level``current_interval_price_level`)
- Price rating sensors (e.g., `current_price_rating`) - Price rating sensors (e.g., `current_price_rating``current_interval_price_rating`)
- Volatility sensors (e.g., `today_s_price_volatility`) - Volatility sensors (e.g., `today_s_price_volatility``today_volatility`)
- Price outlook sensors (e.g., `price_outlook_3h`) - Price outlook sensors (e.g., `price_outlook_3h`)
- Binary sensors (e.g., `best_price_period`, `peak_price_period`) - Binary sensors (e.g., `best_price_period`, `peak_price_period`)
- Timing sensors (e.g., `best_price_time_until_start`, `best_price_progress`) - Timing sensors (e.g., `best_price_time_until_start``best_price_next_in_minutes`, `best_price_progress`)
The colors adapt to the sensor's state - cheaper prices typically show green, expensive prices red, and neutral states gray. The colors adapt to the sensor's state - cheaper prices typically show green, expensive prices red, and neutral states gray.

View file

@ -2,7 +2,7 @@
Learn how Best Price and Peak Price periods work, and how to configure them for your needs. Learn how Best Price and Peak Price periods work, and how to configure them for your needs.
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## Table of Contents ## Table of Contents
@ -28,8 +28,8 @@ Learn how Best Price and Peak Price periods work, and how to configure them for
The integration finds time windows when electricity is especially **cheap** (Best Price) or **expensive** (Peak Price): The integration finds time windows when electricity is especially **cheap** (Best Price) or **expensive** (Peak Price):
- **Best Price Periods** 🟢 - When to run your dishwasher, charge your EV, or heat water - <EntityRef id="best_price_period">Best Price Periods</EntityRef> 🟢 - When to run your dishwasher, charge your EV, or heat water
- **Peak Price Periods** 🔴 - When to reduce consumption or defer non-essential loads - <EntityRef id="peak_price_period">Peak Price Periods</EntityRef> 🔴 - When to reduce consumption or defer non-essential loads
### Default Behavior ### Default Behavior

View file

@ -0,0 +1,261 @@
---
comments: false
---
# Entity Reference (All Languages)
<EntitySearch />
## How to Find Your Entity in Home Assistant
**Entity ID pattern:** `sensor.<home_name>_<suffix>`
- `<home_name>` is generated from your Tibber home display name (lowercase, spaces replaced with underscores)
- `<suffix>` is shown in the **Entity ID suffix** column below
**Three ways to find an entity:**
1. **Search above** — Type the entity name in your language to filter the tables below
2. **Device page** — Go to **Settings → Devices & Services → Tibber Prices**
click your home device → all entities are listed
3. **Developer Tools** — Go to **Developer Tools → States**
type `tibber` in the filter
:::tip
You can also use your browser's built-in search (**Ctrl+F** / **Cmd+F**) to search the full page text.
:::
**Enabled by default:** The ✅ column shows whether a sensor is enabled by default.
Sensors marked ❌ must be enabled manually via
**Settings → Devices & Services → Entities** → find the entity → toggle **Enabled**.
**Detailed documentation:** See the **[Sensors Guide](sensors.md)** for detailed
explanations of each sensor's purpose, attributes, and automation examples.
---
## Sensors
### Core Price Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-current_interval_price" class="entity-anchor"></span>`current_interval_price` | Current Electricity Price | Aktueller Strompreis | Nåværende strømpris | Huidige Elektriciteitsprijs | Aktuellt elpris | ✅ |
| <span id="ref-current_interval_price_base" class="entity-anchor"></span>`current_interval_price_base` | Current Electricity Price (Energy Dashboard) | Aktueller Strompreis (Energie-Dashboard) | Nåværende strømpris (Energi-dashboard) | Huidige Elektriciteitsprijs (Energie Dashboard) | Aktuellt elpris (Energidashboard) | ✅ |
| <span id="ref-next_interval_price" class="entity-anchor"></span>`next_interval_price` | Next Electricity Price | Nächster Strompreis | Neste strømpris | Volgende Elektriciteitsprijs | Nästa elpris | ✅ |
| <span id="ref-previous_interval_price" class="entity-anchor"></span>`previous_interval_price` | Previous Electricity Price | Vorheriger Strompreis | Forrige strømpris | Vorige Elektriciteitsprijs | Föregående elpris | ❌ |
### Hourly Average Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-current_hour_average_price" class="entity-anchor" data-refs="sensors"></span>`current_hour_average_price` | ⌀ Hourly Price Current | ⌀ Stunden-Preis aktuell | ⌀ Timepris nåværende | ⌀ Uurprijs Huidig | ⌀ Timpris aktuell | ✅ |
| <span id="ref-next_hour_average_price" class="entity-anchor" data-refs="sensors"></span>`next_hour_average_price` | ⌀ Hourly Price Next | ⌀ Stunden-Preis nächste Stunde | ⌀ Timepris neste | ⌀ Uurprijs Volgend | ⌀ Timpris nästa | ✅ |
### Daily Statistics
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-lowest_price_today" class="entity-anchor" data-refs="sensors"></span>`lowest_price_today` | Today's Lowest Price | Mindestpreis heute | Dagens laveste pris | Laagste Prijs Vandaag | Dagens lägsta pris | ✅ |
| <span id="ref-highest_price_today" class="entity-anchor" data-refs="sensors"></span>`highest_price_today` | Today's Highest Price | Höchstpreis heute | Dagens høyeste pris | Hoogste Prijs Vandaag | Dagens högsta pris | ✅ |
| <span id="ref-average_price_today" class="entity-anchor" data-refs="sensors"></span>`average_price_today` | ⌀ Price Today | ⌀ Preis heute | ⌀ Pris i dag | ⌀ Prijs Vandaag | ⌀ Pris idag | ✅ |
| <span id="ref-lowest_price_tomorrow" class="entity-anchor" data-refs="sensors"></span>`lowest_price_tomorrow` | Tomorrow's Lowest Price | Mindestpreis morgen | Morgendagens laveste pris | Laagste Prijs Morgen | Morgondagens lägsta pris | ✅ |
| <span id="ref-highest_price_tomorrow" class="entity-anchor" data-refs="sensors"></span>`highest_price_tomorrow` | Tomorrow's Highest Price | Höchstpreis morgen | Morgendagens høyeste pris | Hoogste Prijs Morgen | Morgondagens högsta pris | ✅ |
| <span id="ref-average_price_tomorrow" class="entity-anchor" data-refs="sensors"></span>`average_price_tomorrow` | ⌀ Price Tomorrow | ⌀ Preis morgen | ⌀ Pris i morgen | ⌀ Prijs Morgen | ⌀ Pris imorgon | ✅ |
### 24h Window Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-trailing_price_average" class="entity-anchor" data-refs="sensors"></span>`trailing_price_average` | ⌀ Price Trailing 24h | ⌀ Preis nachlaufend 24h | ⌀ Pris glidende 24t | ⌀ Prijs Afgelopen 24u | ⌀ Pris glidande 24h | ❌ |
| <span id="ref-leading_price_average" class="entity-anchor" data-refs="sensors"></span>`leading_price_average` | ⌀ Price Leading 24h | ⌀ Preis vorlaufend 24h | ⌀ Pris fremtidig 24t | ⌀ Prijs Komende 24u | ⌀ Pris framåt 24h | ❌ |
| <span id="ref-trailing_price_min" class="entity-anchor" data-refs="sensors"></span>`trailing_price_min` | Trailing 24h Minimum Price | 24h-Mindestpreis nachlaufend | Glidende 24t minimumspris | Afgelopen 24u Minimumprijs | Glidande 24h minimipris | ❌ |
| <span id="ref-trailing_price_max" class="entity-anchor" data-refs="sensors"></span>`trailing_price_max` | Trailing 24h Maximum Price | 24h-Höchstpreis nachlaufend | Glidende 24t maksimumspris | Afgelopen 24u Maximumprijs | Glidande 24h maximipris | ❌ |
| <span id="ref-leading_price_min" class="entity-anchor" data-refs="sensors"></span>`leading_price_min` | Leading 24h Minimum Price | 24h-Mindestpreis vorlaufend | Fremtidig 24t minimumspris | Komende 24u Minimumprijs | Framåt 24h minimipris | ❌ |
| <span id="ref-leading_price_max" class="entity-anchor" data-refs="sensors"></span>`leading_price_max` | Leading 24h Maximum Price | 24h-Höchstpreis vorlaufend | Fremtidig 24t maksimumspris | Komende 24u Maximumprijs | Framåt 24h maximipris | ❌ |
### Future Price Averages
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-next_avg_1h" class="entity-anchor"></span>`next_avg_1h` | ⌀ Price Next 1h | ⌀ Preis nächste 1h | ⌀ Pris neste 1t | ⌀ Prijs Komende 1u | ⌀ Pris nästa 1h | ✅ |
| <span id="ref-next_avg_2h" class="entity-anchor"></span>`next_avg_2h` | ⌀ Price Next 2h | ⌀ Preis nächste 2h | ⌀ Pris neste 2t | ⌀ Prijs Komende 2u | ⌀ Pris nästa 2h | ✅ |
| <span id="ref-next_avg_3h" class="entity-anchor"></span>`next_avg_3h` | ⌀ Price Next 3h | ⌀ Preis nächste 3h | ⌀ Pris neste 3t | ⌀ Prijs Komende 3u | ⌀ Pris nästa 3h | ✅ |
| <span id="ref-next_avg_4h" class="entity-anchor"></span>`next_avg_4h` | ⌀ Price Next 4h | ⌀ Preis nächste 4h | ⌀ Pris neste 4t | ⌀ Prijs Komende 4u | ⌀ Pris nästa 4h | ✅ |
| <span id="ref-next_avg_5h" class="entity-anchor"></span>`next_avg_5h` | ⌀ Price Next 5h | ⌀ Preis nächste 5h | ⌀ Pris neste 5t | ⌀ Prijs Komende 5u | ⌀ Pris nästa 5h | ✅ |
| <span id="ref-next_avg_6h" class="entity-anchor"></span>`next_avg_6h` | ⌀ Price Next 6h | ⌀ Preis nächste 6h | ⌀ Pris neste 6t | ⌀ Prijs Komende 6u | ⌀ Pris nästa 6h | ❌ |
| <span id="ref-next_avg_8h" class="entity-anchor"></span>`next_avg_8h` | ⌀ Price Next 8h | ⌀ Preis nächste 8h | ⌀ Pris neste 8t | ⌀ Prijs Komende 8u | ⌀ Pris nästa 8h | ❌ |
| <span id="ref-next_avg_12h" class="entity-anchor"></span>`next_avg_12h` | ⌀ Price Next 12h | ⌀ Preis nächste 12h | ⌀ Pris neste 12t | ⌀ Prijs Komende 12u | ⌀ Pris nästa 12h | ❌ |
### Price Level Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-current_interval_price_level" class="entity-anchor" data-refs="sensors"></span>`current_interval_price_level` | Current Price Level | Aktuelles Preisniveau | Nåværende prisnivå | Huidig Prijsniveau | Aktuell prisnivå | ✅ |
| <span id="ref-next_interval_price_level" class="entity-anchor" data-refs="sensors"></span>`next_interval_price_level` | Next Price Level | Nächstes Preisniveau | Neste prisnivå | Volgend Prijsniveau | Nästa prisnivå | ✅ |
| <span id="ref-previous_interval_price_level" class="entity-anchor" data-refs="sensors"></span>`previous_interval_price_level` | Previous Price Level | Vorheriges Preisniveau | Forrige prisnivå | Vorig Prijsniveau | Föregående prisnivå | ❌ |
| <span id="ref-current_hour_price_level" class="entity-anchor" data-refs="sensors"></span>`current_hour_price_level` | Current Hour Price Level | Aktuelles Stunden-Preisniveau | Nåværende timepris nivå | Huidig Uur Prijsniveau | Aktuell timprisnivå | ✅ |
| <span id="ref-next_hour_price_level" class="entity-anchor" data-refs="sensors"></span>`next_hour_price_level` | Next Hour Price Level | Nächstes Stunden-Preisniveau | Neste timepris nivå | Volgend Uur Prijsniveau | Nästa timprisnivå | ✅ |
| <span id="ref-yesterday_price_level" class="entity-anchor" data-refs="sensors"></span>`yesterday_price_level` | Yesterday's Price Level | Preisniveau gestern | Prisnivå i går | Gisteren Prijsniveau | Gårdagens prisnivå | ❌ |
| <span id="ref-today_price_level" class="entity-anchor" data-refs="sensors"></span>`today_price_level` | Today's Price Level | Preisniveau heute | Prisnivå i dag | Vandaag Prijsniveau | Dagens prisnivå | ✅ |
| <span id="ref-tomorrow_price_level" class="entity-anchor" data-refs="sensors"></span>`tomorrow_price_level` | Tomorrow's Price Level | Preisniveau morgen | Prisnivå i morgen | Morgen Prijsniveau | Morgondagens prisnivå | ✅ |
### Price Rating Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-current_interval_price_rating" class="entity-anchor" data-refs="sensors"></span>`current_interval_price_rating` | Current Price Rating | Aktuelle Preisbewertung | Nåværende prisvurdering | Huidige Prijsbeoordeling | Aktuellt prisbetyg | ❌ |
| <span id="ref-next_interval_price_rating" class="entity-anchor" data-refs="sensors"></span>`next_interval_price_rating` | Next Price Rating | Nächste Preisbewertung | Neste prisvurdering | Volgende Prijsbeoordeling | Nästa prisbetyg | ❌ |
| <span id="ref-previous_interval_price_rating" class="entity-anchor" data-refs="sensors"></span>`previous_interval_price_rating` | Previous Price Rating | Vorherige Preisbewertung | Forrige prisvurdering | Vorige Prijsbeoordeling | Föregående prisbetyg | ❌ |
| <span id="ref-current_hour_price_rating" class="entity-anchor" data-refs="sensors"></span>`current_hour_price_rating` | Current Hour Price Rating | Aktuelle Stunden-Preisbewertung | Nåværende timeprisvurdering | Huidig Uur Prijsbeoordeling | Aktuellt timprisbetyg | ❌ |
| <span id="ref-next_hour_price_rating" class="entity-anchor" data-refs="sensors"></span>`next_hour_price_rating` | Next Hour Price Rating | Nächste Stunden-Preisbewertung | Neste timeprisvurdering | Volgend Uur Prijsbeoordeling | Nästa timprisbetyg | ❌ |
| <span id="ref-yesterday_price_rating" class="entity-anchor" data-refs="sensors"></span>`yesterday_price_rating` | Yesterday's Price Rating | Preisbewertung gestern | Prisvurdering i går | Gisteren Prijsbeoordeling | Gårdagens prisbetyg | ❌ |
| <span id="ref-today_price_rating" class="entity-anchor" data-refs="sensors"></span>`today_price_rating` | Today's Price Rating | Preisbewertung heute | Prisvurdering i dag | Vandaag Prijsbeoordeling | Dagens prisbetyg | ❌ |
| <span id="ref-tomorrow_price_rating" class="entity-anchor" data-refs="sensors"></span>`tomorrow_price_rating` | Tomorrow's Price Rating | Preisbewertung morgen | Prisvurdering i morgen | Morgen Prijsbeoordeling | Morgondagens prisbetyg | ❌ |
| <span id="ref-daily_rating" class="entity-anchor"></span>`daily_rating` | Daily Price Rating | Tägliche Preisbewertung | Daglig prisvurdering | Dagelijkse Prijsbeoordeling | Dagligt prisbetyg | ✅ |
| <span id="ref-monthly_rating" class="entity-anchor"></span>`monthly_rating` | Monthly Price Rating | Monatliche Preisbewertung | Månedlig prisvurdering | Maandelijkse Prijsbeoordeling | Månatligt prisbetyg | ✅ |
### Price Outlook & Trend
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-current_price_trend" class="entity-anchor" data-refs="automation-examples"></span>`current_price_trend` | Current Price Trend | Aktueller Preistrend | Nåværende pristrend | Huidige Prijstrend | Aktuell pristrend | ✅ |
| <span id="ref-next_price_trend_change" class="entity-anchor" data-refs="automation-examples"></span>`next_price_trend_change` | Next Price Trend Change | Nächste Trendänderung | Neste trendendring | Volgende Prijstrend Wijziging | Nästa pristrendändring | ✅ |
| <span id="ref-next_price_trend_change_in" class="entity-anchor"></span>`next_price_trend_change_in` | Next Price Trend Change In | Nächste Trendänderung in | Neste trendendring om | Volgende Prijstrend Wijziging over | Nästa pristrendändring om | ✅ |
| <span id="ref-price_outlook_1h" class="entity-anchor"></span>`price_outlook_1h` | Price Outlook (1h) | Preisausblick (1h) | Prisutblikk (1t) | Prijsvooruitzicht (1u) | Prisöversikt (1h) | ✅ |
| <span id="ref-price_outlook_2h" class="entity-anchor"></span>`price_outlook_2h` | Price Outlook (2h) | Preisausblick (2h) | Prisutblikk (2t) | Prijsvooruitzicht (2u) | Prisöversikt (2h) | ✅ |
| <span id="ref-price_outlook_3h" class="entity-anchor"></span>`price_outlook_3h` | Price Outlook (3h) | Preisausblick (3h) | Prisutblikk (3t) | Prijsvooruitzicht (3u) | Prisöversikt (3h) | ✅ |
| <span id="ref-price_outlook_4h" class="entity-anchor"></span>`price_outlook_4h` | Price Outlook (4h) | Preisausblick (4h) | Prisutblikk (4t) | Prijsvooruitzicht (4u) | Prisöversikt (4h) | ✅ |
| <span id="ref-price_outlook_5h" class="entity-anchor"></span>`price_outlook_5h` | Price Outlook (5h) | Preisausblick (5h) | Prisutblikk (5t) | Prijsvooruitzicht (5u) | Prisöversikt (5h) | ✅ |
| <span id="ref-price_outlook_6h" class="entity-anchor"></span>`price_outlook_6h` | Price Outlook (6h) | Preisausblick (6h) | Prisutblikk (6t) | Prijsvooruitzicht (6u) | Prisöversikt (6h) | ❌ |
| <span id="ref-price_outlook_8h" class="entity-anchor"></span>`price_outlook_8h` | Price Outlook (8h) | Preisausblick (8h) | Prisutblikk (8t) | Prijsvooruitzicht (8u) | Prisöversikt (8h) | ❌ |
| <span id="ref-price_outlook_12h" class="entity-anchor"></span>`price_outlook_12h` | Price Outlook (12h) | Preisausblick (12h) | Prisutblikk (12t) | Prijsvooruitzicht (12u) | Prisöversikt (12h) | ❌ |
| <span id="ref-price_trajectory_2h" class="entity-anchor"></span>`price_trajectory_2h` | Price Trajectory (2h) | Preisverlauf (2h) | Prisforløp (2t) | Prijstrajectorie (2u) | Prisutveckling (2h) | ✅ |
| <span id="ref-price_trajectory_3h" class="entity-anchor"></span>`price_trajectory_3h` | Price Trajectory (3h) | Preisverlauf (3h) | Prisforløp (3t) | Prijstrajectorie (3u) | Prisutveckling (3h) | ✅ |
| <span id="ref-price_trajectory_4h" class="entity-anchor"></span>`price_trajectory_4h` | Price Trajectory (4h) | Preisverlauf (4h) | Prisforløp (4t) | Prijstrajectorie (4u) | Prisutveckling (4h) | ✅ |
| <span id="ref-price_trajectory_5h" class="entity-anchor"></span>`price_trajectory_5h` | Price Trajectory (5h) | Preisverlauf (5h) | Prisforløp (5t) | Prijstrajectorie (5u) | Prisutveckling (5h) | ✅ |
| <span id="ref-price_trajectory_6h" class="entity-anchor"></span>`price_trajectory_6h` | Price Trajectory (6h) | Preisverlauf (6h) | Prisforløp (6t) | Prijstrajectorie (6u) | Prisutveckling (6h) | ❌ |
| <span id="ref-price_trajectory_8h" class="entity-anchor"></span>`price_trajectory_8h` | Price Trajectory (8h) | Preisverlauf (8h) | Prisforløp (8t) | Prijstrajectorie (8u) | Prisutveckling (8h) | ❌ |
| <span id="ref-price_trajectory_12h" class="entity-anchor"></span>`price_trajectory_12h` | Price Trajectory (12h) | Preisverlauf (12h) | Prisforløp (12t) | Prijstrajectorie (12u) | Prisutveckling (12h) | ❌ |
### Volatility Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-today_volatility" class="entity-anchor" data-refs="automation-examples,sensors"></span>`today_volatility` | Today's Price Volatility | Volatilität heute | Volatilitet i dag | Vandaag Prijsvolatiliteit | Dagens prisvolatilitet | ✅ |
| <span id="ref-tomorrow_volatility" class="entity-anchor" data-refs="sensors"></span>`tomorrow_volatility` | Tomorrow's Price Volatility | Volatilität morgen | Volatilitet i morgen | Morgen Prijsvolatiliteit | Morgondagens prisvolatilitet | ❌ |
| <span id="ref-next_24h_volatility" class="entity-anchor"></span>`next_24h_volatility` | Next 24h Price Volatility | Volatilität der nächsten 24h | Volatilitet neste 24t | Komende 24u Prijsvolatiliteit | Nästa 24h prisvolatilitet | ❌ |
| <span id="ref-today_tomorrow_volatility" class="entity-anchor" data-refs="sensors"></span>`today_tomorrow_volatility` | Today+Tomorrow Price Volatility | Volatilität heute+morgen | Volatilitet i dag+i morgen | Vandaag+Morgen Prijsvolatiliteit | Idag+Imorgon prisvolatilitet | ❌ |
### Best Price Timing
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-best_price_end_time" class="entity-anchor" data-refs="sensors"></span>`best_price_end_time` | Best Price End | Bestpreis endet | Beste pris slutter | Beste Prijs Einde | Bästa pris slutar | ✅ |
| <span id="ref-best_price_period_duration" class="entity-anchor" data-refs="sensors"></span>`best_price_period_duration` | Best Price Duration | Bestpreis Dauer | Beste pris varighet | Beste Prijs Duur | Bästa pris varaktighet | ❌ |
| <span id="ref-best_price_remaining_minutes" class="entity-anchor" data-refs="sensors"></span>`best_price_remaining_minutes` | Best Price Remaining Time | Bestpreis verbleibend | Beste pris gjenværende tid | Beste Prijs Resterende Tijd | Bästa pris återstående tid | ✅ |
| <span id="ref-best_price_progress" class="entity-anchor" data-refs="sensors"></span>`best_price_progress` | Best Price Progress | Bestpreis Fortschritt | Beste pris fremgang | Beste Prijs Voortgang | Bästa pris framsteg | ✅ |
| <span id="ref-best_price_next_start_time" class="entity-anchor" data-refs="sensors"></span>`best_price_next_start_time` | Best Price Start | Bestpreis startet | Beste pris starter | Beste Prijs Start | Bästa pris startar | ✅ |
| <span id="ref-best_price_next_in_minutes" class="entity-anchor" data-refs="sensors"></span>`best_price_next_in_minutes` | Best Price Starts In | Bestpreis startet in | Beste pris starter om | Beste Prijs Start Over | Bästa pris startar om | ✅ |
### Peak Price Timing
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-peak_price_end_time" class="entity-anchor" data-refs="sensors"></span>`peak_price_end_time` | Peak Price End | Spitzenpreis endet | Topppris slutter | Piekprijs Einde | Topppris slutar | ✅ |
| <span id="ref-peak_price_period_duration" class="entity-anchor" data-refs="sensors"></span>`peak_price_period_duration` | Peak Price Duration | Spitzenpreis Dauer | Topppris varighet | Piekprijs Duur | Topppris varaktighet | ❌ |
| <span id="ref-peak_price_remaining_minutes" class="entity-anchor" data-refs="sensors"></span>`peak_price_remaining_minutes` | Peak Price Remaining Time | Spitzenpreis verbleibend | Topppris gjenværende tid | Piekprijs Resterende Tijd | Topppris återstående tid | ✅ |
| <span id="ref-peak_price_progress" class="entity-anchor" data-refs="sensors"></span>`peak_price_progress` | Peak Price Progress | Spitzenpreis Fortschritt | Topppris fremgang | Piekprijs Voortgang | Topppris framsteg | ✅ |
| <span id="ref-peak_price_next_start_time" class="entity-anchor" data-refs="sensors"></span>`peak_price_next_start_time` | Peak Price Start | Spitzenpreis startet | Topppris starter | Piekprijs Start | Topppris startar | ✅ |
| <span id="ref-peak_price_next_in_minutes" class="entity-anchor" data-refs="sensors"></span>`peak_price_next_in_minutes` | Peak Price Starts In | Spitzenpreis startet in | Topppris starter om | Piekprijs Start Over | Topppris startar om | ✅ |
### Home & Metering Metadata
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-home_type" class="entity-anchor"></span>`home_type` | Home Type | Wohnungstyp | Boligtype | Huistype | Hemtyp | ❌ |
| <span id="ref-home_size" class="entity-anchor"></span>`home_size` | Home Size | Wohnfläche | Boligareal | Huisgrootte | Hemstorlek | ❌ |
| <span id="ref-main_fuse_size" class="entity-anchor"></span>`main_fuse_size` | Main Fuse Size | Hauptsicherung | Hovedsikring | Hoofdzekering Grootte | Huvudsäkringsstorlek | ❌ |
| <span id="ref-number_of_residents" class="entity-anchor"></span>`number_of_residents` | Number of Residents | Anzahl Bewohner | Antall beboere | Aantal Bewoners | Antal boende | ❌ |
| <span id="ref-primary_heating_source" class="entity-anchor"></span>`primary_heating_source` | Primary Heating Source | Primäre Heizquelle | Primær varmekilde | Primaire Verwarmingsbron | Primär värmekälla | ❌ |
| <span id="ref-grid_company" class="entity-anchor"></span>`grid_company` | Grid Company | Netzbetreiber | Nettselskap | Netbedrijf | Nätbolag | ✅ |
| <span id="ref-grid_area_code" class="entity-anchor"></span>`grid_area_code` | Grid Area Code | Netzgebietscode | Nettområdekode | Netgebiedcode | Nätområdeskod | ❌ |
| <span id="ref-price_area_code" class="entity-anchor"></span>`price_area_code` | Price Area Code | Preiszonencode | Prisområdekode | Prijsgebiedcode | Prisområdeskod | ❌ |
| <span id="ref-consumption_ean" class="entity-anchor"></span>`consumption_ean` | Consumption EAN | Verbrauchs-EAN | Forbruks-EAN | Verbruik EAN | Förbruknings-EAN | ❌ |
| <span id="ref-production_ean" class="entity-anchor"></span>`production_ean` | Production EAN | Erzeugungs-EAN | Produksjons-EAN | Productie EAN | Produktions-EAN | ❌ |
| <span id="ref-energy_tax_type" class="entity-anchor"></span>`energy_tax_type` | Energy Tax Type | Energiesteuertyp | Energiavgiftstype | Energiebelasting Type | Energiskattetyp | ❌ |
| <span id="ref-vat_type" class="entity-anchor"></span>`vat_type` | VAT Type | Mehrwertsteuertyp | MVA-type | BTW Type | Momstyp | ❌ |
| <span id="ref-estimated_annual_consumption" class="entity-anchor"></span>`estimated_annual_consumption` | Estimated Annual Consumption | Geschätzter Jahresverbrauch | Estimert årlig forbruk | Geschat Jaarverbruik | Beräknad årlig förbrukning | ✅ |
| <span id="ref-subscription_status" class="entity-anchor"></span>`subscription_status` | Subscription Status | Abonnementstatus | Abonnementsstatus | Abonnement Status | Abonnemangsstatus | ❌ |
### Data & Diagnostics
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-data_lifecycle_status" class="entity-anchor"></span>`data_lifecycle_status` | Data Lifecycle Status | Datenlebenszyklus-Status | Datalivssyklus-status | Data Levenscyclus Status | Datalivscykelstatus | ✅ |
| <span id="ref-chart_data_export" class="entity-anchor"></span>`chart_data_export` | Chart Data Export | Diagramm-Datenexport | Diagramdataeksport | Grafiekdata Export | Diagramdataexport | ❌ |
| <span id="ref-chart_metadata" class="entity-anchor"></span>`chart_metadata` | Chart Metadata | Diagramm-Metadaten | Diagrammetadata | Grafiek Metadata | Diagrammetadata | ✅ |
## Binary Sensors
### Binary Sensors
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-best_price_period" class="entity-anchor" data-refs="period-calculation,sensors"></span>`best_price_period` | Best Price Period | Bestpreis-Zeitraum | Lavpris-periode | Beste Prijs Periode | Bästa Prisperiod | ✅ |
| <span id="ref-peak_price_period" class="entity-anchor" data-refs="period-calculation,sensors"></span>`peak_price_period` | Peak Price Period | Spitzenpreis-Zeitraum | Toppris-periode | Piekprijs Periode | Topprisperiod | ✅ |
| <span id="ref-connection" class="entity-anchor"></span>`connection` | Tibber API Connection | Tibber-API-Verbindung | Tibber API-tilkobling | Tibber API Verbinding | Tibber API-anslutning | ✅ |
| <span id="ref-tomorrow_data_available" class="entity-anchor"></span>`tomorrow_data_available` | Tomorrow's Data Available | Morgige Daten verfügbar | Morgendagens data tilgjengelig | Morgen Gegevens Beschikbaar | Morgondagens data tillgänglig | ✅ |
| <span id="ref-has_ventilation_system" class="entity-anchor"></span>`has_ventilation_system` | Has Ventilation System | Hat Lüftungsanlage | Har ventilasjonsanlegg | Heeft Ventilatiesysteem | Har ventilationssystem | ❌ |
| <span id="ref-realtime_consumption_enabled" class="entity-anchor"></span>`realtime_consumption_enabled` | Realtime Consumption Enabled | Echtzeitverbrauch aktiviert | Sanntidsforbruk aktivert | Realtime Verbruik Ingeschakeld | Realtidsförbrukning aktiverad | ❌ |
## Number Entities (Configuration Overrides)
> These entities allow runtime adjustment of period calculation parameters without changing the integration configuration. All are **disabled by default**.
### Best Price Configuration
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-best_price_flex_override" class="entity-anchor" data-refs="configuration"></span>`best_price_flex_override` | Best Price: Flexibility | Bestpreis: Flexibilität | Beste pris: Fleksibilitet | Beste prijs: Flexibiliteit | Bästa pris: Flexibilitet | ❌ |
| <span id="ref-best_price_min_distance_override" class="entity-anchor" data-refs="configuration"></span>`best_price_min_distance_override` | Best Price: Minimum Distance | Bestpreis: Mindestabstand | Beste pris: Minimumsavstand | Beste prijs: Minimale afstand | Bästa pris: Minimiavstånd | ❌ |
| <span id="ref-best_price_min_period_length_override" class="entity-anchor" data-refs="configuration"></span>`best_price_min_period_length_override` | Best Price: Minimum Period Length | Bestpreis: Mindestperiodenlänge | Beste pris: Minimum periodelengde | Beste prijs: Minimale periodelengte | Bästa pris: Minsta periodlängd | ❌ |
| <span id="ref-best_price_min_periods_override" class="entity-anchor" data-refs="configuration"></span>`best_price_min_periods_override` | Best Price: Minimum Periods | Bestpreis: Mindestperioden | Beste pris: Minimum perioder | Beste prijs: Minimum periodes | Bästa pris: Minsta antal perioder | ❌ |
| <span id="ref-best_price_relaxation_attempts_override" class="entity-anchor" data-refs="configuration"></span>`best_price_relaxation_attempts_override` | Best Price: Relaxation Attempts | Bestpreis: Lockerungsversuche | Beste pris: Lemping forsøk | Beste prijs: Versoepeling pogingen | Bästa pris: Lättnadsförsök | ❌ |
| <span id="ref-best_price_gap_count_override" class="entity-anchor"></span>`best_price_gap_count_override` | Best Price: Gap Tolerance | Bestpreis: Lückentoleranz | Beste pris: Gaptoleranse | Beste prijs: Gap tolerantie | Bästa pris: Glaptolerans | ❌ |
### Peak Price Configuration
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-peak_price_flex_override" class="entity-anchor" data-refs="configuration"></span>`peak_price_flex_override` | Peak Price: Flexibility | Spitzenpreis: Flexibilität | Topppris: Fleksibilitet | Piekprijs: Flexibiliteit | Topppris: Flexibilitet | ❌ |
| <span id="ref-peak_price_min_distance_override" class="entity-anchor" data-refs="configuration"></span>`peak_price_min_distance_override` | Peak Price: Minimum Distance | Spitzenpreis: Mindestabstand | Topppris: Minimumsavstand | Piekprijs: Minimale afstand | Topppris: Minimiavstånd | ❌ |
| <span id="ref-peak_price_min_period_length_override" class="entity-anchor" data-refs="configuration"></span>`peak_price_min_period_length_override` | Peak Price: Minimum Period Length | Spitzenpreis: Mindestperiodenlänge | Topppris: Minimum periodelengde | Piekprijs: Minimale periodelengte | Topppris: Minsta periodlängd | ❌ |
| <span id="ref-peak_price_min_periods_override" class="entity-anchor" data-refs="configuration"></span>`peak_price_min_periods_override` | Peak Price: Minimum Periods | Spitzenpreis: Mindestperioden | Topppris: Minimum perioder | Piekprijs: Minimum periodes | Topppris: Minsta antal perioder | ❌ |
| <span id="ref-peak_price_relaxation_attempts_override" class="entity-anchor" data-refs="configuration"></span>`peak_price_relaxation_attempts_override` | Peak Price: Relaxation Attempts | Spitzenpreis: Lockerungsversuche | Topppris: Lemping forsøk | Piekprijs: Versoepeling pogingen | Topppris: Lättnadsförsök | ❌ |
| <span id="ref-peak_price_gap_count_override" class="entity-anchor"></span>`peak_price_gap_count_override` | Peak Price: Gap Tolerance | Spitzenpreis: Lückentoleranz | Topppris: Gaptoleranse | Piekprijs: Gap tolerantie | Topppris: Glaptolerans | ❌ |
## Switch Entities (Configuration Overrides)
> These switches control whether the relaxation algorithm is active for period detection. All are **disabled by default**.
### Switches
| Entity ID suffix | 🇬🇧 English | 🇩🇪 Deutsch | 🇳🇴 Norsk | 🇳🇱 Nederlands | 🇸🇪 Svenska | Default |
|---|---|---|---|---|---|---|
| <span id="ref-best_price_enable_relaxation_override" class="entity-anchor"></span>`best_price_enable_relaxation_override` | Best Price: Achieve Minimum Count | Bestpreis: Mindestanzahl erreichen | Beste pris: Oppnå minimumsantall | Beste prijs: Minimum aantal bereiken | Bästa pris: Uppnå minimiantal | ❌ |
| <span id="ref-peak_price_enable_relaxation_override" class="entity-anchor"></span>`peak_price_enable_relaxation_override` | Peak Price: Achieve Minimum Count | Spitzenpreis: Mindestanzahl erreichen | Topppris: Oppnå minimumsantall | Piekprijs: Minimum aantal bereiken | Topppris: Uppnå minimiantal | ❌ |

View file

@ -6,7 +6,7 @@ comments: false
> **Tip:** Many sensors have dynamic icons and colors! See the **[Dynamic Icons Guide](dynamic-icons.md)** and **[Dynamic Icon Colors Guide](icon-colors.md)** to enhance your dashboards. > **Tip:** Many sensors have dynamic icons and colors! See the **[Dynamic Icons Guide](dynamic-icons.md)** and **[Dynamic Icon Colors Guide](icon-colors.md)** to enhance your dashboards.
> **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. Example suffixes below use the English display names (en.json) as a baseline. You can find the real ID in **Settings → Devices & Services → Entities** (or **Developer Tools → States**). > **Entity ID tip:** `<home_name>` is a placeholder for your Tibber home display name in Home Assistant. Entity IDs are derived from the displayed name (localized), so the exact slug may differ. **Can't find a sensor?** Use the **[Entity Reference (All Languages)](sensor-reference.md)** to search by name in your language.
## Binary Sensors ## Binary Sensors
@ -16,8 +16,8 @@ These binary sensors indicate when you're in a detected best or peak price perio
**Quick overview:** **Quick overview:**
- **Best Price Period**: Turns ON during periods with significantly lower prices than the daily average - <EntityRef id="best_price_period">Best Price Period</EntityRef>: Turns ON during periods with significantly lower prices than the daily average
- **Peak Price Period**: Turns ON during periods with significantly higher prices than the daily average - <EntityRef id="peak_price_period">Peak Price Period</EntityRef>: Turns ON during periods with significantly higher prices than the daily average
Both sensors include rich attributes with period details, intervals, relaxation status, and more. Both sensors include rich attributes with period details, intervals, relaxation status, and more.
@ -31,13 +31,13 @@ The integration provides several sensors that calculate average electricity pric
| Sensor | Description | Time Window | | Sensor | Description | Time Window |
|--------|-------------|-------------| |--------|-------------|-------------|
| **Average Price Today** | Typical price for current calendar day | 00:00 - 23:59 today | | <EntityRef id="average_price_today">Average Price Today</EntityRef> | Typical price for current calendar day | 00:00 - 23:59 today |
| **Average Price Tomorrow** | Typical price for next calendar day | 00:00 - 23:59 tomorrow | | <EntityRef id="average_price_tomorrow">Average Price Tomorrow</EntityRef> | Typical price for next calendar day | 00:00 - 23:59 tomorrow |
| **Trailing Price Average** | Typical price for last 24 hours | Rolling 24h backward | | <EntityRef id="trailing_price_average">Trailing Price Average</EntityRef> | Typical price for last 24 hours | Rolling 24h backward |
| **Leading Price Average** | Typical price for next 24 hours | Rolling 24h forward | | <EntityRef id="leading_price_average">Leading Price Average</EntityRef> | Typical price for next 24 hours | Rolling 24h forward |
| **Current Hour Average** | Smoothed price around current time | 5 intervals (~75 min) | | <EntityRef id="current_hour_average_price">Current Hour Average</EntityRef> | Smoothed price around current time | 5 intervals (~75 min) |
| **Next Hour Average** | Smoothed price around next hour | 5 intervals (~75 min) | | <EntityRef id="next_hour_average_price">Next Hour Average</EntityRef> | Smoothed price around next hour | 5 intervals (~75 min) |
| **Next N Hours Average** | Future price forecast | 1h, 2h, 3h, 4h, 5h, 6h, 8h, 12h | | **Next N Hours Average** (`next_avg_1h``next_avg_12h`) | Future price forecast | 1h, 2h, 3h, 4h, 5h, 6h, 8h, 12h |
#### Configurable Display: Median vs Mean #### Configurable Display: Median vs Mean
@ -234,10 +234,10 @@ The sensor's state can be `low`, `moderate`, `high`, or `very_high`, based on co
| Sensor | Description | Time Window | | Sensor | Description | Time Window |
|---|---|---| |---|---|---|
| **Today's Price Volatility** | Volatility for the current calendar day | 00:00 - 23:59 today | | <EntityRef id="today_volatility">Today's Price Volatility</EntityRef> | Volatility for the current calendar day | 00:00 - 23:59 today |
| **Tomorrow's Price Volatility** | Volatility for the next calendar day | 00:00 - 23:59 tomorrow | | <EntityRef id="tomorrow_volatility">Tomorrow's Price Volatility</EntityRef> | Volatility for the next calendar day | 00:00 - 23:59 tomorrow |
| **Next 24h Price Volatility** | Volatility for the next 24 hours from now | Rolling 24h forward | | **Next 24h Price Volatility** (`next_24h_volatility`) | Volatility for the next 24 hours from now | Rolling 24h forward |
| **Today + Tomorrow Price Volatility** | Volatility across both today and tomorrow | Up to 48 hours | | <EntityRef id="today_tomorrow_volatility">Today + Tomorrow Price Volatility</EntityRef> | Volatility across both today and tomorrow | Up to 48 hours |
### Configuration ### Configuration
@ -358,14 +358,14 @@ stateDiagram-v2
| Sensor | Scope | Description | | Sensor | Scope | Description |
|--------|-------|-------------| |--------|-------|-------------|
| **Current Price Rating** | Current interval | Rating of the current 15-minute price | | <EntityRef id="current_interval_price_rating">Current Price Rating</EntityRef> | Current interval | Rating of the current 15-minute price |
| **Next Price Rating** | Next interval | Rating for the upcoming 15-minute price | | <EntityRef id="next_interval_price_rating">Next Price Rating</EntityRef> | Next interval | Rating for the upcoming 15-minute price |
| **Previous Price Rating** | Previous interval | Rating for the past 15-minute price | | <EntityRef id="previous_interval_price_rating">Previous Price Rating</EntityRef> | Previous interval | Rating for the past 15-minute price |
| **Current Hour Price Rating** | Rolling 5-interval | Smoothed rating around the current hour | | <EntityRef id="current_hour_price_rating">Current Hour Price Rating</EntityRef> | Rolling 5-interval | Smoothed rating around the current hour |
| **Next Hour Price Rating** | Rolling 5-interval | Smoothed rating around the next hour | | <EntityRef id="next_hour_price_rating">Next Hour Price Rating</EntityRef> | Rolling 5-interval | Smoothed rating around the next hour |
| **Yesterday's Price Rating** | Calendar day | Aggregated rating for yesterday | | <EntityRef id="yesterday_price_rating">Yesterday's Price Rating</EntityRef> | Calendar day | Aggregated rating for yesterday |
| **Today's Price Rating** | Calendar day | Aggregated rating for today | | <EntityRef id="today_price_rating">Today's Price Rating</EntityRef> | Calendar day | Aggregated rating for today |
| **Tomorrow's Price Rating** | Calendar day | Aggregated rating for tomorrow | | <EntityRef id="tomorrow_price_rating">Tomorrow's Price Rating</EntityRef> | Calendar day | Aggregated rating for tomorrow |
### Ratings vs Levels ### Ratings vs Levels
@ -438,14 +438,14 @@ Level sensors show the **Tibber API's own price classification** with a 5-level
| Sensor | Scope | | Sensor | Scope |
|--------|-------| |--------|-------|
| **Current Price Level** | Current interval | | <EntityRef id="current_interval_price_level">Current Price Level</EntityRef> | Current interval |
| **Next Price Level** | Next interval | | <EntityRef id="next_interval_price_level">Next Price Level</EntityRef> | Next interval |
| **Previous Price Level** | Previous interval | | <EntityRef id="previous_interval_price_level">Previous Price Level</EntityRef> | Previous interval |
| **Current Hour Price Level** | Rolling 5-interval window | | <EntityRef id="current_hour_price_level">Current Hour Price Level</EntityRef> | Rolling 5-interval window |
| **Next Hour Price Level** | Rolling 5-interval window | | <EntityRef id="next_hour_price_level">Next Hour Price Level</EntityRef> | Rolling 5-interval window |
| **Yesterday's Price Level** | Calendar day (aggregated) | | <EntityRef id="yesterday_price_level">Yesterday's Price Level</EntityRef> | Calendar day (aggregated) |
| **Today's Price Level** | Calendar day (aggregated) | | <EntityRef id="today_price_level">Today's Price Level</EntityRef> | Calendar day (aggregated) |
| **Tomorrow's Price Level** | Calendar day (aggregated) | | <EntityRef id="tomorrow_price_level">Tomorrow's Price Level</EntityRef> | Calendar day (aggregated) |
**Gap tolerance** smoothing is applied to prevent isolated level flickers (e.g., a single NORMAL between two CHEAPs → corrected to CHEAP). Configure in [options flow](configuration.md#step-4-price-level-gap-tolerance). **Gap tolerance** smoothing is applied to prevent isolated level flickers (e.g., a single NORMAL between two CHEAPs → corrected to CHEAP). Configure in [options flow](configuration.md#step-4-price-level-gap-tolerance).
@ -457,19 +457,19 @@ These sensors show the lowest and highest prices for calendar days and rolling w
| Sensor | Description | | Sensor | Description |
|--------|-------------| |--------|-------------|
| **Today's Lowest Price** | Minimum price today (00:0023:59) | | <EntityRef id="lowest_price_today">Today's Lowest Price</EntityRef> | Minimum price today (00:0023:59) |
| **Today's Highest Price** | Maximum price today (00:0023:59) | | <EntityRef id="highest_price_today">Today's Highest Price</EntityRef> | Maximum price today (00:0023:59) |
| **Tomorrow's Lowest Price** | Minimum price tomorrow | | <EntityRef id="lowest_price_tomorrow">Tomorrow's Lowest Price</EntityRef> | Minimum price tomorrow |
| **Tomorrow's Highest Price** | Maximum price tomorrow | | <EntityRef id="highest_price_tomorrow">Tomorrow's Highest Price</EntityRef> | Maximum price tomorrow |
### 24-Hour Rolling Min/Max ### 24-Hour Rolling Min/Max
| Sensor | Description | | Sensor | Description |
|--------|-------------| |--------|-------------|
| **Trailing Price Min** | Lowest price in the last 24 hours | | <EntityRef id="trailing_price_min">Trailing Price Min</EntityRef> | Lowest price in the last 24 hours |
| **Trailing Price Max** | Highest price in the last 24 hours | | <EntityRef id="trailing_price_max">Trailing Price Max</EntityRef> | Highest price in the last 24 hours |
| **Leading Price Min** | Lowest price in the next 24 hours | | <EntityRef id="leading_price_min">Leading Price Min</EntityRef> | Lowest price in the next 24 hours |
| **Leading Price Max** | Highest price in the next 24 hours | | <EntityRef id="leading_price_max">Leading Price Max</EntityRef> | Highest price in the next 24 hours |
### Key Attributes ### Key Attributes
@ -608,12 +608,12 @@ For each period type (Best Price and Peak Price):
| Sensor | When Period Active | When No Active Period | | Sensor | When Period Active | When No Active Period |
|--------|-------------------|----------------------| |--------|-------------------|----------------------|
| **End Time** | Current period's end time | Next period's end time | | <EntityRef id="best_price_end_time" also="peak_price_end_time">End Time</EntityRef> | Current period's end time | Next period's end time |
| **Period Duration** | Current period length (minutes) | Next period length | | <EntityRef id="best_price_period_duration" also="peak_price_period_duration">Period Duration</EntityRef> | Current period length (minutes) | Next period length |
| **Remaining Minutes** | Minutes until current period ends | 0 | | <EntityRef id="best_price_remaining_minutes" also="peak_price_remaining_minutes">Remaining Minutes</EntityRef> | Minutes until current period ends | 0 |
| **Progress** | 0100% through current period | 0 | | <EntityRef id="best_price_progress" also="peak_price_progress">Progress</EntityRef> | 0100% through current period | 0 |
| **Next Start Time** | When next-next period starts | When next period starts | | <EntityRef id="best_price_next_start_time" also="peak_price_next_start_time">Next Start Time</EntityRef> | When next-next period starts | When next period starts |
| **Next In Minutes** | Minutes to next-next period | Minutes to next period | | <EntityRef id="best_price_next_in_minutes" also="peak_price_next_in_minutes">Next In Minutes</EntityRef> | Minutes to next-next period | Minutes to next period |
### Usage Examples ### Usage Examples
@ -689,14 +689,14 @@ These sensors compare the **current price** with the **average price** of the ne
| Sensor | Compares Against | | Sensor | Compares Against |
|--------|-----------------| |--------|-----------------|
| **Price Outlook (1h)** | Average of next 1 hour | | **Price Outlook (1h)** (`price_outlook_1h`) | Average of next 1 hour |
| **Price Outlook (2h)** | Average of next 2 hours | | **Price Outlook (2h)** (`price_outlook_2h`) | Average of next 2 hours |
| **Price Outlook (3h)** | Average of next 3 hours | | **Price Outlook (3h)** (`price_outlook_3h`) | Average of next 3 hours |
| **Price Outlook (4h)** | Average of next 4 hours | | **Price Outlook (4h)** (`price_outlook_4h`) | Average of next 4 hours |
| **Price Outlook (5h)** | Average of next 5 hours | | **Price Outlook (5h)** (`price_outlook_5h`) | Average of next 5 hours |
| **Price Outlook (6h)** | Average of next 6 hours | | **Price Outlook (6h)** (`price_outlook_6h`) | Average of next 6 hours |
| **Price Outlook (8h)** | Average of next 8 hours | | **Price Outlook (8h)** (`price_outlook_8h`) | Average of next 8 hours |
| **Price Outlook (12h)** | Average of next 12 hours | | **Price Outlook (12h)** (`price_outlook_12h`) | Average of next 12 hours |
:::info Same Starting Point — All Outlook Sensors Use Your Current Price :::info Same Starting Point — All Outlook Sensors Use Your Current Price
All outlook sensors share the **same base: your current 15-minute price**. They differ only in how far ahead they average. The windows **overlap** — the 3h average includes ALL intervals from the 1h and 2h windows, plus one more hour. All outlook sensors share the **same base: your current 15-minute price**. They differ only in how far ahead they average. The windows **overlap** — the 3h average includes ALL intervals from the 1h and 2h windows, plus one more hour.
@ -761,13 +761,13 @@ These sensors compare the **first half** of the future window against the **seco
| Sensor | Compares | | Sensor | Compares |
|--------|----------| |--------|----------|
| **Price Trajectory (2h)** | Avg of hour 1 vs avg of hour 2 | | **Price Trajectory (2h)** (`price_trajectory_2h`) | Avg of hour 1 vs avg of hour 2 |
| **Price Trajectory (3h)** | Avg of first 1.5h vs avg of second 1.5h | | **Price Trajectory (3h)** (`price_trajectory_3h`) | Avg of first 1.5h vs avg of second 1.5h |
| **Price Trajectory (4h)** | Avg of first 2h vs avg of second 2h | | **Price Trajectory (4h)** (`price_trajectory_4h`) | Avg of first 2h vs avg of second 2h |
| **Price Trajectory (5h)** | Avg of first 2.5h vs avg of second 2.5h | | **Price Trajectory (5h)** (`price_trajectory_5h`) | Avg of first 2.5h vs avg of second 2.5h |
| **Price Trajectory (6h)** | Avg of first 3h vs avg of second 3h | | **Price Trajectory (6h)** (`price_trajectory_6h`) | Avg of first 3h vs avg of second 3h |
| **Price Trajectory (8h)** | Avg of first 4h vs avg of second 4h | | **Price Trajectory (8h)** (`price_trajectory_8h`) | Avg of first 4h vs avg of second 4h |
| **Price Trajectory (12h)** | Avg of first 6h vs avg of second 6h | | **Price Trajectory (12h)** (`price_trajectory_12h`) | Avg of first 6h vs avg of second 6h |
**States:** Same 5-level scale as outlook sensors (`strongly_falling` → `strongly_rising`). **States:** Same 5-level scale as outlook sensors (`strongly_falling` → `strongly_rising`).
@ -1031,7 +1031,7 @@ If you're currently using this sensor, consider migrating to the service:
# New approach (service) # New approach (service)
- service: tibber_prices.get_chartdata - service: tibber_prices.get_chartdata
data: data:
entry_id: YOUR_ENTRY_ID entry_id: YOUR_CONFIG_ENTRY_ID
day: ["today", "tomorrow"] day: ["today", "tomorrow"]
output_format: array_of_objects output_format: array_of_objects
response_variable: chart_data response_variable: chart_data

View file

@ -32,7 +32,7 @@ const sidebars: SidebarsConfig = {
{ {
type: 'category', type: 'category',
label: '📊 Features', label: '📊 Features',
items: ['sensors', 'period-calculation', 'dynamic-icons', 'icon-colors', 'actions'], items: ['sensors', 'sensor-reference', 'period-calculation', 'dynamic-icons', 'icon-colors', 'actions'],
collapsible: true, collapsible: true,
collapsed: false, collapsed: false,
}, },
@ -52,14 +52,14 @@ const sidebars: SidebarsConfig = {
}, },
{ {
type: 'category', type: 'category',
label: '<EFBFBD> Community', label: '👥 Community',
items: ['community-examples'], items: ['community-examples'],
collapsible: true, collapsible: true,
collapsed: false, collapsed: false,
}, },
{ {
type: 'category', type: 'category',
label: '<EFBFBD>🔧 Help & Support', label: '🔧 Help & Support',
items: ['faq', 'troubleshooting'], items: ['faq', 'troubleshooting'],
collapsible: true, collapsible: true,
collapsed: false, collapsed: false,

View file

@ -0,0 +1,48 @@
import React from 'react';
interface EntityRefProps {
/** Primary translation_key / entity ID suffix */
id: string;
/** Optional second key (for paired sensors like best/peak) */
also?: string;
/** Render without <strong> wrapper (default: false → bold) */
noStrong?: boolean;
/** Display name shown to the user */
children: React.ReactNode;
}
/**
* Compact inline reference to an entity, linking to the multi-language
* sensor reference table.
*
* Uses a relative URL so links stay within the current docs version
* (e.g. /next/, /v0.30.0/, or the latest version).
*
* Usage:
* <EntityRef id="average_price_today">Average Price Today</EntityRef>
* <EntityRef id="best_price_end_time" also="peak_price_end_time">End Time</EntityRef>
*/
export default function EntityRef({
id,
also,
noStrong,
children,
}: EntityRefProps): React.ReactElement {
// Relative URL — browser resolves it relative to the current page,
// which automatically preserves the versioned docs path prefix.
const refUrl = `sensor-reference#ref-${id}`;
const keys = also ? `${id} / ${also}` : id;
const tooltip = `${keys} — View in all languages`;
const content = noStrong ? children : <strong>{children}</strong>;
return (
<a
href={refUrl}
className="entity-ref"
title={tooltip}
aria-label={`Entity reference: ${keys}`}
>
{content}
</a>
);
}

View file

@ -0,0 +1,552 @@
import React, {useCallback, useEffect, useRef, useState} from 'react';
interface RowEntry {
/** The entity-anchor id (e.g. "ref-current_interval_price") */
anchorId: string;
/** The translation_key / entity ID suffix */
key: string;
/** English name (first translated name) */
englishName: string;
/** All translated names (for display in results) */
translatedNames: string[];
/** All searchable text from the row (names in all languages, key) */
searchText: string;
/** The <tr> element */
row: HTMLTableRowElement;
/** Platform heading (e.g. "Sensors", "Binary Sensors") */
platform: string;
/** Doc page slugs that reference this entity (from data-refs attribute) */
docRefs: string[];
/** Name cells (columns between key and default) for match highlighting */
nameCells: HTMLTableCellElement[];
/** Original innerHTML of name cells (for restoring after highlighting) */
originalNameHTML: string[];
}
const MAX_RESULTS = 12;
/** Display names for doc page back-links (from data-refs attribute). */
const DOC_NAMES: Record<string, string> = {
sensors: 'Sensors Guide',
configuration: 'Configuration',
'period-calculation': 'Period Calculation',
'automation-examples': 'Automation Examples',
actions: 'Actions',
};
/** Platform filter chips. `match` is tested with startsWith against h2 text. */
const PLATFORM_CHIPS = [
{label: 'Sensors', match: 'Sensors'},
{label: 'Binary Sensors', match: 'Binary Sensors'},
{label: 'Numbers', match: 'Number Entities'},
{label: 'Switches', match: 'Switch Entities'},
];
/**
* Highlight `needle` inside `html` by wrapping matches in <mark> tags.
* Only replaces inside text nodes (outside HTML tags) to keep markup intact.
*/
function highlightHTML(html: string, needle: string): string {
const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(`(${escaped})`, 'gi');
return html.replace(/(<[^>]*>)|([^<]+)/g, (_m, tag: string, text: string) => {
if (tag) return tag;
return text.replace(re, '<mark class="entity-match">$1</mark>');
});
}
/**
* Live-filtering search bar for the sensor-reference page.
*
* Scans all `.entity-anchor` spans on mount to build an index of
* entity keys and translated names. Typing filters the tables in
* real-time and shows a clickable result list to jump to entries.
*/
export default function EntitySearch(): React.ReactElement {
const [query, setQuery] = useState('');
const [total, setTotal] = useState(0);
const [matchCount, setMatchCount] = useState(0);
const [matches, setMatches] = useState<RowEntry[]>([]);
const [activeIndex, setActiveIndex] = useState(-1);
const [activeChip, setActiveChip] = useState<string | null>(null);
const entriesRef = useRef<RowEntry[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const resultsRef = useRef<HTMLUListElement>(null);
// ── Build the search index on mount ──────────────────────────
useEffect(() => {
const anchors = document.querySelectorAll<HTMLSpanElement>('.entity-anchor');
const entries: RowEntry[] = [];
anchors.forEach((anchor) => {
const row = anchor.closest('tr');
if (!row) return;
const anchorId = anchor.id;
const key = anchorId.replace(/^ref-/, '');
// Determine platform from closest h2 above this table
let platform = '';
const table = row.closest('table');
if (table) {
let el = table.previousElementSibling;
while (el) {
if (el.tagName === 'H2') {
platform = el.textContent?.trim() ?? '';
break;
}
el = el.previousElementSibling;
}
}
// Doc back-links from data attribute (set by generator)
const refsAttr = anchor.getAttribute('data-refs');
const docRefs = refsAttr ? refsAttr.split(',').filter(Boolean) : [];
// Collect text from all cells + store name cells for highlighting
const cells = row.querySelectorAll('td');
const texts: string[] = [key];
const translatedNames: string[] = [];
const nameCells: HTMLTableCellElement[] = [];
const originalNameHTML: string[] = [];
cells.forEach((cell, i) => {
const text = cell.textContent?.trim();
if (text && text !== '✅' && text !== '❌') {
texts.push(text);
}
// Name cells = columns between key (0) and default (last)
if (i > 0 && i < cells.length - 1) {
nameCells.push(cell);
originalNameHTML.push(cell.innerHTML);
const t = cell.textContent?.trim();
if (t) translatedNames.push(t);
}
});
// ── Inject copy-entity-ID button ──
const firstCell = cells[0];
if (firstCell && !firstCell.querySelector('.entity-copy-btn')) {
const btn = document.createElement('button');
btn.className = 'entity-copy-btn';
btn.title = 'Copy entity ID suffix';
btn.setAttribute('aria-label', `Copy ${key}`);
btn.textContent = '\u29C9'; // ⧉ overlapping squares
btn.addEventListener('click', (e) => {
e.stopPropagation();
navigator.clipboard.writeText(key).then(() => {
btn.textContent = '\u2713'; // ✓
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = '\u29C9';
btn.classList.remove('copied');
}, 1500);
});
});
firstCell.appendChild(btn);
}
// ── Inject doc back-links ──
if (docRefs.length > 0 && firstCell && !firstCell.querySelector('.entity-back-links')) {
const span = document.createElement('span');
span.className = 'entity-back-links';
docRefs.forEach((slug) => {
const a = document.createElement('a');
a.href = slug;
a.className = 'entity-back-link';
a.title = DOC_NAMES[slug] ?? slug;
a.setAttribute('aria-label', `View in: ${DOC_NAMES[slug] ?? slug}`);
a.textContent = '\uD83D\uDCD6'; // 📖
span.appendChild(a);
});
firstCell.appendChild(span);
}
entries.push({
anchorId,
key,
englishName: translatedNames[0] ?? key,
translatedNames,
searchText: texts.join(' ').toLowerCase(),
row,
platform,
docRefs,
nameCells,
originalNameHTML,
});
});
entriesRef.current = entries;
setTotal(entries.length);
setMatchCount(entries.length);
}, []);
// ── Jump to #ref-* hash on arrival / hash change ─────────────
useEffect(() => {
const entries = entriesRef.current;
if (entries.length === 0) return;
const jumpToHash = () => {
const hash = window.location.hash;
if (!hash.startsWith('#ref-')) return;
const key = hash.slice(5);
const entry = entries.find((e) => e.key === key);
if (!entry) return;
document.querySelectorAll('.entity-search-jump-highlight').forEach((el) => {
el.classList.remove('entity-search-jump-highlight');
});
const anchor = document.getElementById(entry.anchorId);
if (anchor) {
requestAnimationFrame(() => {
anchor.scrollIntoView({behavior: 'smooth', block: 'center'});
void entry.row.offsetWidth;
entry.row.classList.add('entity-search-jump-highlight');
});
}
};
jumpToHash();
window.addEventListener('hashchange', jumpToHash);
return () => window.removeEventListener('hashchange', jumpToHash);
}, [total]);
// ── Global "/" shortcut to focus the search input ────────────
useEffect(() => {
const handleSlash = (e: KeyboardEvent) => {
if (
e.key === '/' &&
!e.ctrlKey &&
!e.metaKey &&
!e.altKey &&
!(e.target instanceof HTMLInputElement) &&
!(e.target instanceof HTMLTextAreaElement) &&
!(e.target instanceof HTMLSelectElement)
) {
e.preventDefault();
inputRef.current?.focus();
inputRef.current?.scrollIntoView({behavior: 'smooth', block: 'nearest'});
}
};
document.addEventListener('keydown', handleSlash);
return () => document.removeEventListener('keydown', handleSlash);
}, []);
// ── Core filtering logic ─────────────────────────────────────
const applyFilter = useCallback((search: string, chip: string | null) => {
const entries = entriesRef.current;
const needle = search.toLowerCase().trim();
let matchCountLocal = 0;
const matchedEntries: RowEntry[] = [];
const sectionsWithMatches = new Set<Element>();
// Restore previous match highlights
entries.forEach((entry) => {
entry.nameCells.forEach((cell, i) => {
if (cell.innerHTML !== entry.originalNameHTML[i]) {
cell.innerHTML = entry.originalNameHTML[i];
}
});
});
entries.forEach((entry) => {
// Platform chip filter
const chipMatch = !chip || entry.platform.startsWith(chip);
// Text search filter
const textMatch = !needle || entry.searchText.includes(needle);
const isMatch = chipMatch && textMatch;
if (isMatch) {
matchCountLocal++;
matchedEntries.push(entry);
entry.row.classList.remove('entity-search-hidden');
entry.row.classList.add('entity-search-match');
// Highlight matched text in name cells
if (needle) {
entry.nameCells.forEach((cell, i) => {
cell.innerHTML = highlightHTML(entry.originalNameHTML[i], needle);
});
}
const table = entry.row.closest('table');
if (table) {
let prev = table.previousElementSibling;
while (prev && prev.tagName !== 'H3' && prev.tagName !== 'H2') {
prev = prev.previousElementSibling;
}
if (prev) sectionsWithMatches.add(prev);
}
} else {
entry.row.classList.add('entity-search-hidden');
entry.row.classList.remove('entity-search-match');
}
});
const isActive = !!(needle || chip);
if (isActive) {
document.querySelectorAll('.markdown h3, .markdown h2').forEach((heading) => {
if (heading.textContent?.includes('How to Find')) return;
const hasMatch = sectionsWithMatches.has(heading);
if (heading.tagName === 'H3') {
heading.classList.toggle('entity-search-section-hidden', !hasMatch);
let el = heading.nextElementSibling;
while (el && el.tagName !== 'TABLE' && el.tagName !== 'H3' && el.tagName !== 'H2') {
el.classList.toggle('entity-search-section-hidden', !hasMatch);
el = el.nextElementSibling;
}
}
});
} else {
document.querySelectorAll('.entity-search-section-hidden').forEach((el) => {
el.classList.remove('entity-search-section-hidden');
});
}
setMatchCount(matchCountLocal);
setMatches(isActive ? matchedEntries : []);
setActiveIndex(-1);
}, []);
const scrollToEntry = useCallback((entry: RowEntry) => {
document.querySelectorAll('.entity-search-jump-highlight').forEach((el) => {
el.classList.remove('entity-search-jump-highlight');
});
const anchor = document.getElementById(entry.anchorId);
if (anchor) {
history.pushState(null, '', `#${entry.anchorId}`);
anchor.scrollIntoView({behavior: 'smooth', block: 'center'});
void entry.row.offsetWidth;
entry.row.classList.add('entity-search-jump-highlight');
}
}, []);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
applyFilter(value, activeChip);
},
[applyFilter, activeChip],
);
const handleClear = useCallback(() => {
setQuery('');
setActiveChip(null);
applyFilter('', null);
inputRef.current?.focus();
}, [applyFilter]);
// Click anywhere outside the search bar → reset all filters
useEffect(() => {
if (query.trim().length === 0 && activeChip === null) return;
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as Node;
// Don't reset if clicking inside the search container
if (containerRef.current?.contains(target)) return;
// Don't reset if clicking inside an entity table or its buttons
if ((target as Element).closest?.('.entity-copy-btn, .entity-back-link, table')) return;
setQuery('');
setActiveChip(null);
applyFilter('', null);
};
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, [query, activeChip, applyFilter]);
const handleChipClick = useCallback(
(chipMatch: string) => {
const next = chipMatch === activeChip ? null : chipMatch;
setActiveChip(next);
applyFilter(query, next);
},
[applyFilter, query, activeChip],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Escape') {
handleClear();
return;
}
const visibleMatches = matches.slice(0, MAX_RESULTS);
if (visibleMatches.length === 0) return;
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveIndex((prev) => {
const next = prev < visibleMatches.length - 1 ? prev + 1 : 0;
resultsRef.current?.children[next]?.scrollIntoView({block: 'nearest'});
return next;
});
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveIndex((prev) => {
const next = prev > 0 ? prev - 1 : visibleMatches.length - 1;
resultsRef.current?.children[next]?.scrollIntoView({block: 'nearest'});
return next;
});
} else if (e.key === 'Enter' && activeIndex >= 0 && activeIndex < visibleMatches.length) {
e.preventDefault();
scrollToEntry(visibleMatches[activeIndex]);
}
},
[handleClear, matches, activeIndex, scrollToEntry],
);
/** Find the best matching translated name for highlighting in dropdown */
const getMatchingName = useCallback(
(entry: RowEntry, needle: string): string | null => {
if (!needle) return null;
const lower = needle.toLowerCase();
// Check non-English names first (user is likely searching in their language)
for (let i = 1; i < entry.translatedNames.length; i++) {
if (entry.translatedNames[i].toLowerCase().includes(lower)) {
return entry.translatedNames[i];
}
}
// Then English
if (entry.englishName.toLowerCase().includes(lower)) {
return null; // Already shown as primary
}
return null;
},
[],
);
const isFiltering = query.trim().length > 0 || activeChip !== null;
const visibleMatches = matches.slice(0, MAX_RESULTS);
const hasMore = matches.length > MAX_RESULTS;
return (
<div ref={containerRef} className="entity-search">
{/* ── Category filter chips ── */}
<div className="entity-search-chips" role="group" aria-label="Filter by platform">
{PLATFORM_CHIPS.map((chip) => (
<button
key={chip.match}
type="button"
className={`entity-search-chip${activeChip === chip.match ? ' active' : ''}`}
onClick={() => handleChipClick(chip.match)}
aria-pressed={activeChip === chip.match}
>
{chip.label}
</button>
))}
<span className="entity-search-shortcut-hint">
Press <kbd>/</kbd> to search
</span>
</div>
{/* ── Search input ── */}
<div className="entity-search-input-wrapper">
<svg
className="entity-search-icon"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
<input
ref={inputRef}
type="text"
className="entity-search-input"
placeholder="Search entities (any language)…"
value={query}
onChange={handleChange}
onKeyDown={handleKeyDown}
aria-label="Search entities"
aria-expanded={isFiltering && visibleMatches.length > 0}
aria-controls="entity-search-results"
aria-activedescendant={
activeIndex >= 0 ? `entity-result-${activeIndex}` : undefined
}
autoComplete="off"
spellCheck={false}
role="combobox"
/>
{isFiltering && (
<button
className="entity-search-clear"
onClick={handleClear}
aria-label="Clear search and filters"
type="button"
>
</button>
)}
</div>
{/* ── Results dropdown ── */}
{isFiltering && (
<div className="entity-search-results-container">
{matchCount === 0 ? (
<div className="entity-search-status">
<span className="entity-search-no-results">No matching entities found</span>
</div>
) : (
<>
{query.trim().length > 0 && (
<ul
ref={resultsRef}
id="entity-search-results"
className="entity-search-results"
role="listbox"
>
{visibleMatches.map((entry, i) => {
const matchedTranslation = getMatchingName(entry, query);
return (
<li
key={entry.key}
id={`entity-result-${i}`}
className={`entity-search-result-item${i === activeIndex ? ' active' : ''}`}
role="option"
aria-selected={i === activeIndex}
onClick={() => scrollToEntry(entry)}
onMouseEnter={() => setActiveIndex(i)}
>
<span className="entity-search-result-name">
{entry.englishName}
</span>
<code className="entity-search-result-key">{entry.key}</code>
{matchedTranslation && (
<span className="entity-search-result-translation">
{matchedTranslation}
</span>
)}
</li>
);
})}
</ul>
)}
<div className="entity-search-status">
{matchCount} of {total} entities
{hasMore && query.trim().length > 0 && (
<span className="entity-search-more">
{' '}&mdash; showing first {MAX_RESULTS}, type more to narrow down
</span>
)}
</div>
</>
)}
</div>
)}
</div>
);
}

View file

@ -4,6 +4,440 @@
* work well for content-centric websites. * work well for content-centric websites.
*/ */
/* ── EntityRef component ──────────────────────────────────────── */
/* The link wrapping the entity name */
.entity-ref {
text-decoration: none;
color: inherit;
border-bottom: 1.5px dotted var(--ifm-color-primary);
transition: border-color 0.15s ease, color 0.15s ease;
}
.entity-ref:hover {
border-bottom-style: solid;
color: var(--ifm-color-primary);
text-decoration: none;
}
/* Small arrow indicator after the name */
.entity-ref::after {
content: '\2197'; /* ↗ */
display: inline-block;
font-size: 0.6em;
vertical-align: super;
margin-left: 0.15em;
opacity: 0.4;
transition: opacity 0.15s ease;
}
.entity-ref:hover::after {
opacity: 1;
}
/* Inside tables, keep the indicator subtle */
td .entity-ref::after {
font-size: 0.55em;
}
/* ── EntitySearch component ────────────────────────────────────── */
.entity-search {
position: sticky;
top: calc(var(--ifm-navbar-height) + 0.5rem);
z-index: 10;
margin-bottom: 1.5rem;
padding: 0.75rem 1rem;
background: var(--ifm-background-surface-color);
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: var(--ifm-global-radius);
box-shadow: var(--ifm-global-shadow-md);
backdrop-filter: blur(12px);
}
[data-theme='dark'] .entity-search {
background: rgba(36, 36, 36, 0.92);
border-color: var(--ifm-color-emphasis-300);
}
.entity-search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.entity-search-icon {
position: absolute;
left: 0.75rem;
color: var(--ifm-color-emphasis-500);
pointer-events: none;
flex-shrink: 0;
}
.entity-search-input {
width: 100%;
padding: 0.6rem 2.5rem 0.6rem 2.5rem;
border: 1.5px solid var(--ifm-color-emphasis-300);
border-radius: calc(var(--ifm-global-radius) - 2px);
background: var(--ifm-background-color);
color: var(--ifm-font-color-base);
font-size: 0.95rem;
font-family: var(--ifm-font-family-base);
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.entity-search-input::placeholder {
color: var(--ifm-color-emphasis-500);
}
.entity-search-input:focus {
border-color: var(--ifm-color-primary);
box-shadow: 0 0 0 3px rgba(0, 185, 231, 0.15);
}
[data-theme='dark'] .entity-search-input:focus {
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.15);
}
.entity-search-clear {
position: absolute;
right: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
padding: 0;
border: none;
border-radius: 50%;
background: var(--ifm-color-emphasis-200);
color: var(--ifm-color-emphasis-700);
font-size: 0.75rem;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.entity-search-clear:hover {
background: var(--ifm-color-emphasis-300);
color: var(--ifm-color-emphasis-900);
}
.entity-search-status {
margin-top: 0.4rem;
font-size: 0.8rem;
color: var(--ifm-color-emphasis-600);
padding-left: 0.25rem;
}
.entity-search-no-results {
color: var(--ifm-color-danger);
}
/* ── Results dropdown ─────────────────────────────────────────── */
.entity-search-results-container {
margin-top: 0.5rem;
}
.entity-search-results {
list-style: none;
margin: 0;
padding: 0.25rem 0;
max-height: 24rem;
overflow-y: auto;
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: calc(var(--ifm-global-radius) - 2px);
background: var(--ifm-background-color);
}
[data-theme='dark'] .entity-search-results {
border-color: var(--ifm-color-emphasis-300);
}
.entity-search-result-item {
display: flex;
align-items: baseline;
gap: 0.5rem;
padding: 0.45rem 0.75rem;
cursor: pointer;
transition: background 0.1s ease;
flex-wrap: wrap;
}
.entity-search-result-item:hover,
.entity-search-result-item.active {
background: var(--ifm-color-primary-lightest);
color: var(--ifm-font-color-base);
}
[data-theme='dark'] .entity-search-result-item:hover,
[data-theme='dark'] .entity-search-result-item.active {
background: rgba(0, 212, 255, 0.12);
}
.entity-search-result-name {
font-weight: 600;
font-size: 0.9rem;
white-space: nowrap;
}
.entity-search-result-key {
font-size: 0.75rem;
opacity: 0.6;
white-space: nowrap;
}
.entity-search-result-translation {
font-size: 0.8rem;
color: var(--ifm-color-emphasis-600);
margin-left: auto;
text-align: right;
white-space: nowrap;
}
.entity-search-more {
color: var(--ifm-color-emphasis-500);
}
/* ── Jump highlight (triggered by clicking a result) ──────────── */
tr.entity-search-jump-highlight {
animation: entity-jump-flash 1.8s ease forwards;
}
@keyframes entity-jump-flash {
0% { background-color: rgba(0, 185, 231, 0.45); }
30% { background-color: rgba(0, 185, 231, 0.25); }
100% { background-color: rgba(0, 185, 231, 0.13); }
}
[data-theme='dark'] tr.entity-search-jump-highlight {
animation: entity-jump-flash-dark 1.8s ease forwards;
}
@keyframes entity-jump-flash-dark {
0% { background-color: rgba(0, 212, 255, 0.40); }
30% { background-color: rgba(0, 212, 255, 0.20); }
100% { background-color: rgba(0, 212, 255, 0.13); }
}
/* Row filtering states */
tr.entity-search-hidden {
opacity: 0.08;
transition: opacity 0.2s ease;
}
tr.entity-search-match {
transition: opacity 0.2s ease;
}
/* Dim sections with no matches */
.entity-search-section-hidden {
opacity: 0.15;
transition: opacity 0.2s ease;
}
/* ── Filter chips ─────────────────────────────────────────────── */
.entity-search-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin-bottom: 0.6rem;
align-items: center;
}
.entity-search-chip {
padding: 0.25rem 0.7rem;
border: 1.5px solid var(--ifm-color-emphasis-300);
border-radius: 2rem;
background: transparent;
color: var(--ifm-color-emphasis-700);
font-size: 0.8rem;
font-family: var(--ifm-font-family-base);
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.entity-search-chip:hover {
border-color: var(--ifm-color-primary);
color: var(--ifm-color-primary);
background: rgba(0, 185, 231, 0.06);
}
.entity-search-chip.active {
border-color: var(--ifm-color-primary);
background: var(--ifm-color-primary);
color: white;
}
[data-theme='dark'] .entity-search-chip.active {
background: var(--ifm-color-primary-dark);
}
.entity-search-shortcut-hint {
margin-left: auto;
font-size: 0.75rem;
color: var(--ifm-color-emphasis-400);
white-space: nowrap;
}
.entity-search-shortcut-hint kbd {
display: inline-block;
padding: 0.05rem 0.35rem;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 0.25rem;
background: var(--ifm-color-emphasis-100);
font-family: var(--ifm-font-family-monospace);
font-size: 0.75rem;
color: var(--ifm-color-emphasis-600);
line-height: 1.4;
vertical-align: baseline;
}
/* ── Copy entity ID button ────────────────────────────────────── */
.entity-copy-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4rem;
height: 1.4rem;
margin-left: 0.35rem;
padding: 0;
border: none;
border-radius: 0.25rem;
background: transparent;
color: var(--ifm-color-emphasis-400);
font-size: 0.8rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
vertical-align: middle;
}
tr:hover .entity-copy-btn {
opacity: 1;
}
/* Always visible on touch devices (no hover) */
@media (hover: none) {
.entity-copy-btn {
opacity: 0.6;
}
}
.entity-copy-btn:hover {
background: var(--ifm-color-emphasis-200);
color: var(--ifm-color-emphasis-700);
}
.entity-copy-btn.copied {
color: var(--ifm-color-success);
opacity: 1;
}
/* ── Doc back-links ───────────────────────────────────────────── */
.entity-back-links {
margin-left: 0.25rem;
white-space: nowrap;
}
.entity-back-link {
display: inline-block;
font-size: 0.7rem;
text-decoration: none;
opacity: 0;
transition: opacity 0.15s ease;
margin-left: 0.1rem;
vertical-align: middle;
}
tr:hover .entity-back-link {
opacity: 0.7;
}
.entity-back-link:hover {
opacity: 1 !important;
}
@media (hover: none) {
.entity-back-link {
opacity: 0.5;
}
}
/* ── Match highlighting ──────────────────────────────────────── */
mark.entity-match {
background-color: rgba(255, 184, 0, 0.35);
color: inherit;
padding: 0.05em 0.1em;
border-radius: 2px;
}
[data-theme='dark'] mark.entity-match {
background-color: rgba(255, 184, 0, 0.3);
}
/* ── Mobile responsive ────────────────────────────────────────── */
@media (max-width: 768px) {
.entity-search {
position: relative;
top: auto;
}
.entity-search-chips {
overflow-x: auto;
flex-wrap: nowrap;
-webkit-overflow-scrolling: touch;
padding-bottom: 0.2rem;
}
.entity-search-shortcut-hint {
display: none;
}
.entity-search-results {
max-height: 16rem;
}
}
/* ── Sensor-reference :target highlighting ────────────────────── */
/* Scroll offset so the targeted row isn't hidden behind the navbar */
.entity-anchor {
scroll-margin-top: 5rem;
}
/* Highlight the table row that was navigated to (stays visible) */
tr:has(.entity-anchor:target) {
animation: entity-highlight-pulse 2s ease forwards;
}
@keyframes entity-highlight-pulse {
0% { background-color: rgba(0, 185, 231, 0.35); }
100% { background-color: rgba(0, 185, 231, 0.13); }
}
[data-theme='dark'] tr:has(.entity-anchor:target) {
animation: entity-highlight-pulse-dark 2s ease forwards;
}
@keyframes entity-highlight-pulse-dark {
0% { background-color: rgba(0, 212, 255, 0.30); }
100% { background-color: rgba(0, 212, 255, 0.13); }
}
/* Smooth scrolling for anchor navigation */
html {
scroll-behavior: smooth;
}
/* Modern font stack */ /* Modern font stack */
:root { :root {
--ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; --ifm-font-family-base: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

View file

@ -0,0 +1,13 @@
/**
* Extend the default MDXComponents so that <EntityRef> and <EntitySearch>
* are available in every .mdx / .md page without explicit imports.
*/
import MDXComponents from '@theme-original/MDXComponents';
import EntityRef from '@site/src/components/EntityRef';
import EntitySearch from '@site/src/components/EntitySearch';
export default {
...MDXComponents,
EntityRef,
EntitySearch,
};

View file

@ -27,7 +27,7 @@ typeCheckingMode = "basic"
[tool.ruff] [tool.ruff]
# Based on https://github.com/home-assistant/core/blob/dev/pyproject.toml # Based on https://github.com/home-assistant/core/blob/dev/pyproject.toml
target-version = "py313" target-version = "py314"
line-length = 120 line-length = 120
[tool.ruff.lint] [tool.ruff.lint]
@ -46,6 +46,10 @@ ignore = [
"S101", # assert is fine in tests "S101", # assert is fine in tests
"PLR2004", # Magic values are fine in tests "PLR2004", # Magic values are fine in tests
] ]
"scripts/*" = [
"T201", # print() is the correct output method for CLI scripts
"INP001", # scripts/ is not a Python package (no __init__.py)
]
[tool.ruff.lint.flake8-pytest-style] [tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false fixture-parentheses = false

View file

@ -27,5 +27,9 @@ fi
"$SCRIPT_DIR/type-check" "$SCRIPT_DIR/type-check"
echo "" echo ""
"$SCRIPT_DIR/lint-check" "$SCRIPT_DIR/lint-check"
echo ""
log_header "Checking sensor reference freshness..."
python3 "$SCRIPT_DIR/docs/generate-sensor-reference" --check
log_success "All checks passed" log_success "All checks passed"

View file

@ -0,0 +1,581 @@
#!/usr/bin/env python3
"""
Generate the multi-language sensor reference page from translation files.
Reads entity translations from all language files and entity definitions
to produce a searchable reference table in docs/user/docs/sensor-reference.md.
Usage:
scripts/docs/generate-sensor-reference # Generate/update the file
scripts/docs/generate-sensor-reference --check # Verify file is up-to-date (CI)
"""
from __future__ import annotations
import json
import re
import sys
from collections import OrderedDict
from pathlib import Path
# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
TRANSLATIONS_DIR = REPO_ROOT / "custom_components" / "tibber_prices" / "translations"
OUTPUT_FILE = REPO_ROOT / "docs" / "user" / "docs" / "sensor-reference.md"
LANGUAGES = OrderedDict(
[
("en", "🇬🇧 English"),
("de", "🇩🇪 Deutsch"),
("nb", "🇳🇴 Norsk"),
("nl", "🇳🇱 Nederlands"),
("sv", "🇸🇪 Svenska"),
]
)
# ---------------------------------------------------------------------------
# Definitions files (for entity_registry_enabled_default extraction)
# ---------------------------------------------------------------------------
DEFINITIONS_FILES: dict[str, Path] = {
"sensor": REPO_ROOT / "custom_components" / "tibber_prices" / "sensor" / "definitions.py",
"binary_sensor": REPO_ROOT / "custom_components" / "tibber_prices" / "binary_sensor" / "definitions.py",
"number": REPO_ROOT / "custom_components" / "tibber_prices" / "number" / "definitions.py",
"switch": REPO_ROOT / "custom_components" / "tibber_prices" / "switch" / "definitions.py",
}
# ---------------------------------------------------------------------------
# Category mapping: translation_key → (category_name, sort_order)
#
# Keys not listed here will appear in an "Other" category at the end.
# Order within a category follows insertion order in this dict.
# ---------------------------------------------------------------------------
SENSOR_CATEGORIES: OrderedDict[str, list[str]] = OrderedDict(
[
(
"Core Price Sensors",
[
"current_interval_price",
"current_interval_price_base",
"next_interval_price",
"previous_interval_price",
],
),
(
"Hourly Average Sensors",
[
"current_hour_average_price",
"next_hour_average_price",
],
),
(
"Daily Statistics",
[
"lowest_price_today",
"highest_price_today",
"average_price_today",
"lowest_price_tomorrow",
"highest_price_tomorrow",
"average_price_tomorrow",
],
),
(
"24h Window Sensors",
[
"trailing_price_average",
"leading_price_average",
"trailing_price_min",
"trailing_price_max",
"leading_price_min",
"leading_price_max",
],
),
(
"Future Price Averages",
[
"next_avg_1h",
"next_avg_2h",
"next_avg_3h",
"next_avg_4h",
"next_avg_5h",
"next_avg_6h",
"next_avg_8h",
"next_avg_12h",
],
),
(
"Price Level Sensors",
[
"current_interval_price_level",
"next_interval_price_level",
"previous_interval_price_level",
"current_hour_price_level",
"next_hour_price_level",
"yesterday_price_level",
"today_price_level",
"tomorrow_price_level",
],
),
(
"Price Rating Sensors",
[
"current_interval_price_rating",
"next_interval_price_rating",
"previous_interval_price_rating",
"current_hour_price_rating",
"next_hour_price_rating",
"yesterday_price_rating",
"today_price_rating",
"tomorrow_price_rating",
"daily_rating",
"monthly_rating",
],
),
(
"Price Outlook & Trend",
[
"current_price_trend",
"next_price_trend_change",
"next_price_trend_change_in",
"price_outlook_1h",
"price_outlook_2h",
"price_outlook_3h",
"price_outlook_4h",
"price_outlook_5h",
"price_outlook_6h",
"price_outlook_8h",
"price_outlook_12h",
"price_trajectory_2h",
"price_trajectory_3h",
"price_trajectory_4h",
"price_trajectory_5h",
"price_trajectory_6h",
"price_trajectory_8h",
"price_trajectory_12h",
],
),
(
"Volatility Sensors",
[
"today_volatility",
"tomorrow_volatility",
"next_24h_volatility",
"today_tomorrow_volatility",
],
),
(
"Best Price Timing",
[
"best_price_end_time",
"best_price_period_duration",
"best_price_remaining_minutes",
"best_price_progress",
"best_price_next_start_time",
"best_price_next_in_minutes",
],
),
(
"Peak Price Timing",
[
"peak_price_end_time",
"peak_price_period_duration",
"peak_price_remaining_minutes",
"peak_price_progress",
"peak_price_next_start_time",
"peak_price_next_in_minutes",
],
),
(
"Home & Metering Metadata",
[
"home_type",
"home_size",
"main_fuse_size",
"number_of_residents",
"primary_heating_source",
"grid_company",
"grid_area_code",
"price_area_code",
"consumption_ean",
"production_ean",
"energy_tax_type",
"vat_type",
"estimated_annual_consumption",
"subscription_status",
],
),
(
"Data & Diagnostics",
[
"data_lifecycle_status",
"chart_data_export",
"chart_metadata",
],
),
]
)
BINARY_SENSOR_CATEGORIES: OrderedDict[str, list[str]] = OrderedDict(
[
(
"Binary Sensors",
[
"best_price_period",
"peak_price_period",
"connection",
"tomorrow_data_available",
"has_ventilation_system",
"realtime_consumption_enabled",
],
),
]
)
NUMBER_CATEGORIES: OrderedDict[str, list[str]] = OrderedDict(
[
(
"Best Price Configuration",
[
"best_price_flex_override",
"best_price_min_distance_override",
"best_price_min_period_length_override",
"best_price_min_periods_override",
"best_price_relaxation_attempts_override",
"best_price_gap_count_override",
],
),
(
"Peak Price Configuration",
[
"peak_price_flex_override",
"peak_price_min_distance_override",
"peak_price_min_period_length_override",
"peak_price_min_periods_override",
"peak_price_relaxation_attempts_override",
"peak_price_gap_count_override",
],
),
]
)
SWITCH_CATEGORIES: OrderedDict[str, list[str]] = OrderedDict(
[
(
"Switches",
[
"best_price_enable_relaxation_override",
"peak_price_enable_relaxation_override",
],
),
]
)
# ---------------------------------------------------------------------------
# Data loading
# ---------------------------------------------------------------------------
def load_translations() -> dict[str, dict[str, dict[str, dict]]]:
"""
Load entity translations from all language files.
Returns: {lang: {platform: {key: {"name": "..."}}}}
"""
result: dict[str, dict[str, dict[str, dict]]] = {}
for lang in LANGUAGES:
filepath = TRANSLATIONS_DIR / f"{lang}.json"
with filepath.open(encoding="utf-8") as f:
data = json.load(f)
entity_section = data.get("entity", {})
result[lang] = {}
for platform in ("sensor", "binary_sensor", "number", "switch"):
result[lang][platform] = entity_section.get(platform, {})
return result
def extract_disabled_entities(definitions_path: Path) -> set[str]:
"""
Extract entity keys that have entity_registry_enabled_default=False.
Uses regex parsing — no Python import needed.
"""
disabled: set[str] = set()
if not definitions_path.exists():
return disabled
text = definitions_path.read_text(encoding="utf-8")
# Find all key= assignments, then check if the block before the next
# key= contains entity_registry_enabled_default=False.
key_pattern = re.compile(r'key="([^"]+)"')
disabled_pattern = re.compile(r"entity_registry_enabled_default\s*=\s*False")
keys_with_pos = [(m.group(1), m.start()) for m in key_pattern.finditer(text)]
for i, (key, start) in enumerate(keys_with_pos):
# Get the text between this key and the next key (or end of file)
end = keys_with_pos[i + 1][1] if i + 1 < len(keys_with_pos) else len(text)
block = text[start:end]
if disabled_pattern.search(block):
disabled.add(key)
# If neither pattern found, default is True (enabled)
return disabled
def load_all_disabled() -> dict[str, set[str]]:
"""Load disabled-by-default entity keys for all platforms."""
result: dict[str, set[str]] = {}
for platform, path in DEFINITIONS_FILES.items():
result[platform] = extract_disabled_entities(path)
return result
def scan_doc_refs() -> dict[str, list[str]]:
"""
Scan doc markdown files for EntityRef usage.
Returns: {entity_key: [doc_slug, ...]}
"""
refs: dict[str, list[str]] = {}
docs_dir = REPO_ROOT / "docs" / "user" / "docs"
entity_ref_pattern = re.compile(r'<EntityRef\s[^>]*?\bid="([^"]+)"')
also_pattern = re.compile(r'\balso="([^"]+)"')
for md_file in sorted(docs_dir.glob("*.md")):
if md_file.name == "sensor-reference.md":
continue
slug = md_file.stem
text = md_file.read_text(encoding="utf-8")
for match in entity_ref_pattern.finditer(text):
key = match.group(1)
refs.setdefault(key, [])
if slug not in refs[key]:
refs[key].append(slug)
# Check for 'also' prop in the same tag
tag_end = text.find(">", match.start())
if tag_end != -1:
tag_text = text[match.start() : tag_end]
also_match = also_pattern.search(tag_text)
if also_match:
also_key = also_match.group(1)
refs.setdefault(also_key, [])
if slug not in refs[also_key]:
refs[also_key].append(slug)
return refs
# ---------------------------------------------------------------------------
# Markdown generation
# ---------------------------------------------------------------------------
FRONTMATTER = """\
---
comments: false
---
"""
INTRO = """\
# Entity Reference (All Languages)
<EntitySearch />
## How to Find Your Entity in Home Assistant
**Entity ID pattern:** `sensor.<home_name>_<suffix>`
- `<home_name>` is generated from your Tibber home display name (lowercase, spaces replaced with underscores)
- `<suffix>` is shown in the **Entity ID suffix** column below
**Three ways to find an entity:**
1. **Search above** — Type the entity name in your language to filter the tables below
2. **Device page** — Go to **Settings → Devices & Services → Tibber Prices** →
click your home device → all entities are listed
3. **Developer Tools** — Go to **Developer Tools → States** →
type `tibber` in the filter
:::tip
You can also use your browser's built-in search (**Ctrl+F** / **Cmd+F**) to search the full page text.
:::
**Enabled by default:** The ✅ column shows whether a sensor is enabled by default.
Sensors marked ❌ must be enabled manually via
**Settings → Devices & Services → Entities** → find the entity → toggle **Enabled**.
**Detailed documentation:** See the **[Sensors Guide](sensors.md)** for detailed
explanations of each sensor's purpose, attributes, and automation examples.
---
"""
def generate_table(
categories: OrderedDict[str, list[str]],
platform: str,
translations: dict[str, dict[str, dict[str, dict]]],
disabled: dict[str, set[str]],
doc_refs: dict[str, list[str]],
) -> str:
"""Generate a grouped Markdown table for one platform."""
lines: list[str] = []
platform_disabled = disabled.get(platform, set())
lang_codes = list(LANGUAGES.keys())
lang_headers = list(LANGUAGES.values())
# Collect uncategorized keys
all_categorized: set[str] = set()
for keys in categories.values():
all_categorized.update(keys)
# Get all keys from English translations for this platform
en_keys = set(translations.get("en", {}).get(platform, {}).keys())
uncategorized = en_keys - all_categorized
for category_name, keys in categories.items():
lines.append(f"### {category_name}\n")
lines.append("")
# Table header
header = "| Entity ID suffix | " + " | ".join(lang_headers) + " | Default |"
separator = "|---|" + "|".join(["---"] * len(lang_codes)) + "|---|"
lines.append(header)
lines.append(separator)
for key in keys:
names: list[str] = []
for lang in lang_codes:
platform_trans = translations.get(lang, {}).get(platform, {})
entity_data = platform_trans.get(key, {})
name = entity_data.get("name", "—")
names.append(name)
enabled = "❌" if key in platform_disabled else "✅"
ref_list = doc_refs.get(key, [])
data_refs_attr = f' data-refs="{",".join(ref_list)}"' if ref_list else ""
anchor = f'<span id="ref-{key}" class="entity-anchor"{data_refs_attr}></span>'
row = f"| {anchor}`{key}` | " + " | ".join(names) + f" | {enabled} |"
lines.append(row)
lines.append("")
# Add uncategorized keys if any
if uncategorized:
lines.append("### Other\n")
lines.append("")
header = "| Entity ID suffix | " + " | ".join(lang_headers) + " | Default |"
separator = "|---|" + "|".join(["---"] * len(lang_codes)) + "|---|"
lines.append(header)
lines.append(separator)
for key in sorted(uncategorized):
names = []
for lang in lang_codes:
platform_trans = translations.get(lang, {}).get(platform, {})
entity_data = platform_trans.get(key, {})
name = entity_data.get("name", "—")
names.append(name)
enabled = "❌" if key in platform_disabled else "✅"
ref_list = doc_refs.get(key, [])
data_refs_attr = f' data-refs="{",".join(ref_list)}"' if ref_list else ""
anchor = f'<span id="ref-{key}" class="entity-anchor"{data_refs_attr}></span>'
row = f"| {anchor}`{key}` | " + " | ".join(names) + f" | {enabled} |"
lines.append(row)
lines.append("")
return "\n".join(lines)
def generate_full_document(
translations: dict[str, dict[str, dict[str, dict]]],
disabled: dict[str, set[str]],
) -> str:
"""Generate the complete sensor-reference.md content."""
doc_refs = scan_doc_refs()
parts: list[str] = []
parts.append(FRONTMATTER)
parts.append(INTRO)
# Sensors
parts.append("## Sensors\n\n")
parts.append(generate_table(SENSOR_CATEGORIES, "sensor", translations, disabled, doc_refs))
# Binary Sensors
parts.append("## Binary Sensors\n\n")
parts.append(generate_table(BINARY_SENSOR_CATEGORIES, "binary_sensor", translations, disabled, doc_refs))
# Number Entities
parts.append("## Number Entities (Configuration Overrides)\n\n")
parts.append(
"> These entities allow runtime adjustment of period calculation parameters without "
"changing the integration configuration. All are **disabled by default**.\n\n"
)
parts.append(generate_table(NUMBER_CATEGORIES, "number", translations, disabled, doc_refs))
# Switch Entities
parts.append("## Switch Entities (Configuration Overrides)\n\n")
parts.append(
"> These switches control whether the relaxation algorithm is active for period detection. "
"All are **disabled by default**.\n\n"
)
parts.append(generate_table(SWITCH_CATEGORIES, "switch", translations, disabled, doc_refs))
return "".join(parts)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
"""Generate or check the sensor-reference.md file."""
check_mode = "--check" in sys.argv
translations = load_translations()
disabled = load_all_disabled()
content = generate_full_document(translations, disabled)
if check_mode:
if not OUTPUT_FILE.exists():
print(f"✗ Sensor reference not found: {OUTPUT_FILE}")
print(" Run: scripts/docs/generate-sensor-reference")
return 1
existing = OUTPUT_FILE.read_text(encoding="utf-8")
if existing == content:
print("✓ Sensor reference is up to date")
return 0
print(f"✗ Sensor reference is outdated: {OUTPUT_FILE}")
print(" Run: scripts/docs/generate-sensor-reference")
return 1
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_FILE.write_text(content, encoding="utf-8")
# Count entities
total = 0
for platform in ("sensor", "binary_sensor", "number", "switch"):
count = len(translations.get("en", {}).get(platform, {}))
total += count
print(f"✓ Generated {OUTPUT_FILE.relative_to(REPO_ROOT)} ({total} entities, {len(LANGUAGES)} languages)")
return 0
if __name__ == "__main__":
sys.exit(main())