Skip to content

Instantly share code, notes, and snippets.

@jpawlowski
Last active April 17, 2025 11:55
Show Gist options
  • Save jpawlowski/e7c1369d3fbd5213a3a40f96b668c3b9 to your computer and use it in GitHub Desktop.
Save jpawlowski/e7c1369d3fbd5213a3a40f96b668c3b9 to your computer and use it in GitHub Desktop.
HASS Tibber Price Information & Rating
alias: tibber_api_timer
description: ""
triggers:
- trigger: time_pattern
hours: "0"
minutes: "0"
seconds: "5"
- trigger: time_pattern
hours: "0"
minutes: "1"
seconds: "1"
- trigger: time_pattern
hours: "13"
minutes: "10"
seconds: "5"
- trigger: time_pattern
hours: "13"
minutes: "45"
seconds: "1"
- trigger: time_pattern
hours: "15"
minutes: "2"
seconds: "5"
- trigger: time_pattern
hours: "15"
minutes: "15"
seconds: "1"
- trigger: homeassistant
event: start
conditions: []
actions:
- action: homeassistant.update_entity
data:
entity_id:
- sensor.tibber_price_data_pricerating_monthly
enabled: true
- delay:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
- action: homeassistant.update_entity
data:
entity_id:
- sensor.tibber_price_data_priceinfo
enabled: true
- delay:
hours: 0
minutes: 0
seconds: 10
milliseconds: 0
- action: homeassistant.update_entity
data:
entity_id:
- sensor.tibber_price_data_pricerating_hourly
enabled: true
- delay:
hours: 0
minutes: 0
seconds: 15
milliseconds: 0
- action: homeassistant.update_entity
data:
entity_id:
- sensor.tibber_price_data_pricerating_daily
enabled: true
mode: single
platform: rest
unique_id: tibber_price_data_priceinfo
name: Tibber Price Data PriceInfo
icon: mdi:database
resource: https://api.tibber.com/v1-beta/gql
method: POST
payload: >
{
"query": "{ viewer { homes { currentSubscription { priceInfo { range(resolution: HOURLY, last: 48) { edges { node { startsAt total energy tax level } } } today { startsAt total energy tax level } tomorrow { startsAt total energy tax level } } } } } }"
}
json_attributes_path: "$.data.viewer.homes[0].currentSubscription"
json_attributes:
- priceInfo
value_template: >-
{% set data =
value_json.data.viewer.homes[0].currentSubscription.priceInfo
if value_json is defined and
value_json.data is defined and
value_json.data.viewer is defined and
value_json.data.viewer.homes is defined and
value_json.data.viewer.homes | length > 0 and
value_json.data.viewer.homes[0] is defined and
value_json.data.viewer.homes[0].currentSubscription is defined and
value_json.data.viewer.homes[0].currentSubscription.priceInfo is defined
else none %}
{{ data.tomorrow[0].startsAt[:10]
if data is defined and data is not none and
data.tomorrow is defined and data.tomorrow | length > 0
else data.today[0].startsAt[:10]
if data is defined and data.today is defined and data.today | length > 0
else 'unavailable' }}
scan_interval: 99999
headers:
Authorization: !secret tibber_token
Content-Type: application/json
Accept: application/json
User-Agent: HomeAssistant/unknown (RESTful Sensor; +https://home-assistant.io/integrations/sensor.rest/)
platform: rest
unique_id: tibber_price_data_pricerating_daily
name: Tibber Price Data PriceRating Daily
icon: mdi:database
resource: https://api.tibber.com/v1-beta/gql
method: POST
payload: >
{
"query": "{ viewer { homes { currentSubscription { priceRating { thresholdPercentages { low high } daily { entries { time total energy tax difference level } } } } } } }"
}
json_attributes_path: "$.data.viewer.homes[0].currentSubscription"
json_attributes:
- priceRating
value_template: >-
{% set data =
value_json.data.viewer.homes[0].currentSubscription.priceRating.daily.entries
if value_json is defined and
value_json.data is defined and
value_json.data.viewer is defined and
value_json.data.viewer.homes is defined and
value_json.data.viewer.homes | length > 0 and
value_json.data.viewer.homes[0] is defined and
value_json.data.viewer.homes[0].currentSubscription is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.daily is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.daily.entries is defined
else none %}
{% set today = now().strftime('%Y-%m-%dT00:00:00') %}
{% set tomorrow = (now() + timedelta(days=1)).strftime('%Y-%m-%dT00:00:00') %}
{% set has_tomorrow = data | selectattr('time', 'search', tomorrow) | list | first
if data is defined and data is not none else none %}
{% set has_today = data | selectattr('time', 'search', today) | list | first
if data is defined and data is not none else none %}
{{ tomorrow[:10]
if has_tomorrow is defined and has_tomorrow is not none
else today[:10]
if has_today is defined and has_today is not none
else 'unavailable' }}
scan_interval: 99999
headers:
Authorization: !secret tibber_token
Content-Type: application/json
Accept: application/json
User-Agent: HomeAssistant/unknown (RESTful Sensor; +https://home-assistant.io/integrations/sensor.rest/)
platform: rest
unique_id: tibber_price_data_pricerating_hourly
name: Tibber Price Data PriceRating Hourly
icon: mdi:database
resource: https://api.tibber.com/v1-beta/gql
method: POST
payload: >
{
"query": "{ viewer { homes { currentSubscription { priceRating { thresholdPercentages { low high } hourly { entries { time total energy tax difference level } } } } } } }"
}
json_attributes_path: "$.data.viewer.homes[0].currentSubscription"
json_attributes:
- priceRating
value_template: >-
{% set data =
value_json.data.viewer.homes[0].currentSubscription.priceRating.hourly.entries
if value_json is defined and
value_json.data is defined and
value_json.data.viewer is defined and
value_json.data.viewer.homes is defined and
value_json.data.viewer.homes | length > 0 and
value_json.data.viewer.homes[0] is defined and
value_json.data.viewer.homes[0].currentSubscription is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.hourly is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.hourly.entries is defined
else none %}
{% set today = now().strftime('%Y-%m-%dT00:00:00') %}
{% set tomorrow = (now() + timedelta(days=1)).strftime('%Y-%m-%dT00:00:00') %}
{% set has_tomorrow = data | selectattr('time', 'search', tomorrow) | list | first
if data is defined and data is not none else none %}
{% set has_today = data | selectattr('time', 'search', today) | list | first
if data is defined and data is not none else none %}
{{ tomorrow[:10]
if has_tomorrow is defined and has_tomorrow is not none
else today[:10]
if has_today is defined and has_today is not none
else 'unavailable' }}
scan_interval: 99999
headers:
Authorization: !secret tibber_token
Content-Type: application/json
Accept: application/json
User-Agent: HomeAssistant/unknown (RESTful Sensor; +https://home-assistant.io/integrations/sensor.rest/)
platform: rest
unique_id: tibber_price_data_pricerating_monthly
name: Tibber Price Data PriceRating Monthly
icon: mdi:database
resource: https://api.tibber.com/v1-beta/gql
method: POST
payload: >
{
"query": "{ viewer { homes { currentSubscription { priceRating { thresholdPercentages { low high } monthly { currency entries { time total energy tax difference level } } } } } } }"
}
json_attributes_path: "$.data.viewer.homes[0].currentSubscription"
json_attributes:
- priceRating
value_template: >-
{% set data = value_json.data.viewer.homes[0].currentSubscription.priceRating.monthly.entries
if value_json is defined and
value_json.data is defined and
value_json.data.viewer is defined and
value_json.data.viewer.homes is defined and
value_json.data.viewer.homes | length > 0 and
value_json.data.viewer.homes[0].currentSubscription is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.monthly is defined and
value_json.data.viewer.homes[0].currentSubscription.priceRating.monthly.entries is defined
else none %}
{% if data is not none and data | length > 0 %}
{{ data[-1].time[:7] }}
{% else %}
unavailable
{% endif %}
scan_interval: 99999
headers:
Authorization: !secret tibber_token
Content-Type: application/json
Accept: application/json
User-Agent: HomeAssistant/unknown (RESTful Sensor; +https://home-assistant.io/integrations/sensor.rest/)
- trigger:
- trigger: time_pattern
minutes: 0
- trigger: state
entity_id:
- sensor.tibber_price_data_pricerating_monthly
binary_sensor:
- name: "Tibber Price Month Available"
unique_id: tibber_price_month_available
icon: mdi:calendar-month
state: >
{% set p = state_attr('sensor.tibber_price_data_pricerating_monthly', 'priceRating') %}
{% set e = p.monthly.entries if p is not none and p.monthly is defined else none %}
{% set current_month = now().strftime('%Y-%m') %}
{{ e is not none and e | selectattr('time', 'search', current_month) | list | count > 0 }}
- trigger:
- trigger: time_pattern
minutes: 0
- trigger: state
entity_id:
- sensor.tibber_price_data_priceinfo
- sensor.tibber_price_data_pricerating_hourly
- sensor.tibber_price_data_pricerating_daily
sensor:
- name: "Tibber Price Today"
unique_id: tibber_price_today
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{{ ((data | map(attribute='total') | average | round(4)) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
icon: mdi:chart-bar
attributes:
friendly_name: "Strompreis heute (Ø)"
description: "Average electricity price for the entire day."
description_de: "Durchschnittlicher Strompreis für den gesamten Tag."
deviation_to_min: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set pmin = data | min(attribute='total') %}
{% set pavg = data | map(attribute='total') | average | round(4) %}
{{ ((pavg - pmin.total) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
deviation_to_max: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set pmax = data | max(attribute='total') %}
{% set pavg = data | map(attribute='total') | average | round(4) %}
{{ ((pmax.total - pavg) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
yesterday_avg: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% if last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{{ ((last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average | round(4)) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
yesterday_deviation_to_min: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% if last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{% set pmin = last_48h | selectattr('startsAt', 'search', yesterday_date) | min(attribute='total') %}
{% set pavg = last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average | round(4) %}
{{ ((pavg - pmin.total) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
yesterday_deviation_to_max: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% if last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{% set pmax = last_48h | selectattr('startsAt', 'search', yesterday_date) | max(attribute='total') %}
{% set pavg = last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average | round(4) %}
{{ ((pmax.total - pavg) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Minimum Today"
unique_id: tibber_price_minimum_today
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{{ entry.total * 100 }}
{% else %}
unavailable
{% endif %}
icon: mdi:triangle-small-down
attributes:
friendly_name: "Günstigste Stunde heute"
description: "Lowest hourly price of today."
description_de: "Günstigste Stunde des heutigen Tages."
energy: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{{ entry.energy * 100 }}
{% else %}
unavailable
{% endif %}
tax: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{{ entry.tax * 100 }}
{% else %}
unavailable
{% endif %}
start_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{{ entry.startsAt }}
{% else %}
unavailable
{% endif %}
2nd_total: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | min(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | min(attribute='total') %}
{{ secondary.total }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_energy: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | min(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | min(attribute='total') %}
{{ secondary.energy }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_tax: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | min(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | min(attribute='total') %}
{{ secondary.tax }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_start_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | min(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | min(attribute='total') %}
{{ secondary.startsAt }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
- name: "Tibber Price Maximum Today"
unique_id: tibber_price_maximum_today
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{{ entry.total * 100 }}
{% else %}
unavailable
{% endif %}
icon: mdi:triangle-small-up
attributes:
friendly_name: "Teuerste Stunde heute"
description: "Highest hourly price of today."
description_de: "Teuerste Stunde des heutigen Tages."
energy: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{{ entry.energy * 100 }}
{% else %}
unavailable
{% endif %}
tax: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{{ entry.tax * 100 }}
{% else %}
unavailable
{% endif %}
start_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{{ entry.startsAt }}
{% else %}
unavailable
{% endif %}
2nd_total: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Teuersten Eintrag finden #}
{% set primary = data | max(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitteuersten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | max(attribute='total') %}
{{ secondary.total }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_energy: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Teuersten Eintrag finden #}
{% set primary = data | max(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitteuersten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | max(attribute='total') %}
{{ secondary.energy }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_tax: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Teuersten Eintrag finden #}
{% set primary = data | max(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitteuersten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | max(attribute='total') %}
{{ secondary.tax }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
2nd_start_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Teuersten Eintrag finden #}
{% set primary = data | max(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitteuersten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | max(attribute='total') %}
{{ secondary.startsAt }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
- name: "Tibber Price Relative Spread Today"
unique_id: tibber_price_relative_spread_today
unit_of_measurement: "%"
state_class: measurement
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set pmax = data | max(attribute='total') %}
{% set pmin = data | min(attribute='total') %}
{% set pavg = data | map(attribute='total') | average %}
{{ (((pmax.total - pmin.total) / pavg) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
icon: mdi:percent
attributes:
friendly_name: "Relative Preisspanne heute"
description: "Percentage spread between max and min price relative to today's average."
description_de: "Prozentuale Spannweite zwischen Höchst- und Tiefstpreis im Verhältnis zum Tagesdurchschnitt."
yesterday: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% if last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{% set pmax = last_48h | selectattr('startsAt', 'search', yesterday_date) | max(attribute='total') %}
{% set pmin = last_48h | selectattr('startsAt', 'search', yesterday_date) | min(attribute='total') %}
{% set pavg = last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average %}
{{ (((pmax.total - pmin.total) / pavg) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Difference Today"
unique_id: tibber_price_difference_today
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data and data | length > 0 and last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{% set yesterday_avg = last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average %}
{% set today_avg = data | map(attribute='total') | average %}
{{ ((today_avg - yesterday_avg) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
icon: mdi:vector-difference
attributes:
friendly_name: "Preisdifferenz heute"
description: "Shows the difference between today's and yesterday's average electricity price."
description_de: "Zeigt die Differenz zwischen dem heutigen und dem gestrigen durchschnittlichen Strompreis."
- name: "Tibber Price Skew Today"
unique_id: tibber_price_skew_today
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set pmax = data | max(attribute='total') %}
{% set pmin = data | min(attribute='total') %}
{% set pavg = data | map(attribute='total') | average | round(4) %}
{{ (((pmax.total - pavg) - (pavg - pmin.total)) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
icon: mdi:chart-bar
attributes:
friendly_name: "Verteilungsschiefe heute"
description: "Deviation of the distribution from the mean value."
description_de: "Abweichung der Verteilung vom Mittelwert."
yesterday: >
{% set last_48h = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('range', {}).get('edges', []) | map(attribute='node') | list %}
{% if last_48h and last_48h | length > 0 %}
{% set yesterday_date = (now() + timedelta(days=-1)).strftime('%Y-%m-%d') %}
{% set pmax = last_48h | selectattr('startsAt', 'search', yesterday_date) | max(attribute='total') %}
{% set pmin = last_48h | selectattr('startsAt', 'search', yesterday_date) | min(attribute='total') %}
{% set pavg = last_48h | selectattr('startsAt', 'search', yesterday_date) | map(attribute='total') | average | round(4) %}
{{ (((pmax.total - pavg) - (pavg - pmin.total)) * 100) | round(2) }}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Distribution Bias Today"
unique_id: tibber_price_distribution_bias_today
device_class: enum
state: >
{% set skew = states('sensor.tibber_price_skew_today') | float(0) %}
{% if skew > 0.5 %}
upward
{% elif skew < -0.5 %}
downward
{% else %}
neutral
{% endif %}
icon: mdi:chart-bell-curve
attributes:
friendly_name: "Verzerrungsrichtung heute"
description: "Describes the price distribution shape for today: neutral, upward or downward skew."
description_de: "Bewertung der Tagesverteilung: neutral, nach oben oder nach unten verzerrt."
friendly_state: >
{% set skew = states('sensor.tibber_price_skew_today') | float(0) %}
{% if skew > 0.5 %}
Obenlastig
{% elif skew < -0.5 %}
Untenlastig
{% else %}
Ausgeglichen
{% endif %}
- name: "Tibber Price Level Today"
unique_id: tibber_price_level_today
device_class: enum
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set score = namespace(total=0) %}
{% for p in data %}
{% if p.level == 'VERY_CHEAP' %}
{% set score.total = score.total - 2 %}
{% elif p.level == 'CHEAP' %}
{% set score.total = score.total - 1 %}
{% elif p.level == 'EXPENSIVE' %}
{% set score.total = score.total + 1 %}
{% elif p.level == 'VERY_EXPENSIVE' %}
{% set score.total = score.total + 2 %}
{% endif %}
{% endfor %}
{% set raw_level =
'VERY_CHEAP' if score.total <= -10 else
'CHEAP' if score.total <= -4 else
'NORMAL' if score.total < 4 else
'EXPENSIVE' if score.total < 10 else
'VERY_EXPENSIVE' %}
{% set present_levels = data | map(attribute='level') | list %}
{% set fallback_map = {
'VERY_CHEAP': ['VERY_CHEAP', 'CHEAP', 'NORMAL'],
'CHEAP': ['CHEAP', 'NORMAL'],
'NORMAL': ['NORMAL'],
'EXPENSIVE': ['EXPENSIVE', 'NORMAL'],
'VERY_EXPENSIVE': ['VERY_EXPENSIVE', 'EXPENSIVE', 'NORMAL']
} %}
{% set fallback_levels = fallback_map[raw_level] %}
{% set actual = namespace(value='NORMAL') %}
{% for lvl in fallback_levels %}
{% if lvl in present_levels %}
{% set actual.value = lvl %}
{% break %}
{% endif %}
{% endfor %}
{{ actual.value }}
{% else %}
unavailable
{% endif %}
icon: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set score = namespace(total=0) %}
{% for p in data %}
{% if p.level == 'VERY_CHEAP' %}
{% set score.total = score.total - 2 %}
{% elif p.level == 'CHEAP' %}
{% set score.total = score.total - 1 %}
{% elif p.level == 'EXPENSIVE' %}
{% set score.total = score.total + 1 %}
{% elif p.level == 'VERY_EXPENSIVE' %}
{% set score.total = score.total + 2 %}
{% endif %}
{% endfor %}
{% set raw_level =
'VERY_CHEAP' if score.total <= -10 else
'CHEAP' if score.total <= -4 else
'NORMAL' if score.total < 4 else
'EXPENSIVE' if score.total < 10 else
'VERY_EXPENSIVE' %}
{% set present_levels = data | map(attribute='level') | list %}
{% set fallback_map = {
'VERY_CHEAP': ['VERY_CHEAP', 'CHEAP', 'NORMAL'],
'CHEAP': ['CHEAP', 'NORMAL'],
'NORMAL': ['NORMAL'],
'EXPENSIVE': ['EXPENSIVE', 'NORMAL'],
'VERY_EXPENSIVE': ['VERY_EXPENSIVE', 'EXPENSIVE', 'NORMAL']
} %}
{% set fallback_levels = fallback_map[raw_level] %}
{% set actual = namespace(value='NORMAL') %}
{% for lvl in fallback_levels %}
{% if lvl in present_levels %}
{% set actual.value = lvl %}
{% break %}
{% endif %}
{% endfor %}
{% if actual.value == 'VERY_CHEAP' %}
mdi:arrow-bottom-right-thick
{% elif actual.value == 'CHEAP' %}
mdi:trending-down
{% elif actual.value == 'NORMAL' %}
mdi:plus-minus-variant
{% elif actual.value == 'EXPENSIVE' %}
mdi:trending-up
{% elif actual.value == 'VERY_EXPENSIVE' %}
mdi:arrow-top-right-thick
{% else %}
mdi:help-circle
{% endif %}
{% else %}
mdi:help-circle
{% endif %}
attributes:
friendly_name: "Dominantes Preisniveau heute"
description: "Long-term level of today's prices (compared to last 7 days)."
description_de: "Langfristige Einordnung des heutigen Preises (7-Tage-Vergleich)."
valid_states: '["VERY_CHEAP", "CHEAP", "NORMAL", "EXPENSIVE", "VERY_EXPENSIVE"]'
friendly_state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set score = namespace(total=0) %}
{% for p in data %}
{% if p.level == 'VERY_CHEAP' %}
{% set score.total = score.total - 2 %}
{% elif p.level == 'CHEAP' %}
{% set score.total = score.total - 1 %}
{% elif p.level == 'EXPENSIVE' %}
{% set score.total = score.total + 1 %}
{% elif p.level == 'VERY_EXPENSIVE' %}
{% set score.total = score.total + 2 %}
{% endif %}
{% endfor %}
{% set raw_level =
'VERY_CHEAP' if score.total <= -10 else
'CHEAP' if score.total <= -4 else
'NORMAL' if score.total < 4 else
'EXPENSIVE' if score.total < 10 else
'VERY_EXPENSIVE' %}
{% set present_levels = data | map(attribute='level') | list %}
{% set fallback_map = {
'VERY_CHEAP': ['VERY_CHEAP', 'CHEAP', 'NORMAL'],
'CHEAP': ['CHEAP', 'NORMAL'],
'NORMAL': ['NORMAL'],
'EXPENSIVE': ['EXPENSIVE', 'NORMAL'],
'VERY_EXPENSIVE': ['VERY_EXPENSIVE', 'EXPENSIVE', 'NORMAL']
} %}
{% set fallback_levels = fallback_map[raw_level] %}
{% set actual = namespace(value='NORMAL') %}
{% for lvl in fallback_levels %}
{% if lvl in present_levels %}
{% set actual.value = lvl %}
{% break %}
{% endif %}
{% endfor %}
{% if actual.value == 'VERY_CHEAP' %}
Sehr günstig
{% elif actual.value == 'CHEAP' %}
Günstig
{% elif actual.value == 'NORMAL' %}
Normal
{% elif actual.value == 'EXPENSIVE' %}
Teuer
{% elif actual.value == 'VERY_EXPENSIVE' %}
Sehr teuer
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
state_absolute: >-
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set score = namespace(total=0) %}
{% for p in data %}
{% if p.level == 'VERY_CHEAP' %}
{% set score.total = score.total - 2 %}
{% elif p.level == 'CHEAP' %}
{% set score.total = score.total - 1 %}
{% elif p.level == 'EXPENSIVE' %}
{% set score.total = score.total + 1 %}
{% elif p.level == 'VERY_EXPENSIVE' %}
{% set score.total = score.total + 2 %}
{% endif %}
{% endfor %}
{{ score.total }}
{% else %}
unavailable
{% endif %}
counts: |-
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set counts = namespace(
VERY_CHEAP=0,
CHEAP=0,
NORMAL=0,
EXPENSIVE=0,
VERY_EXPENSIVE=0
) %}
{% for p in data %}
{% if p.level == 'VERY_CHEAP' %}
{% set counts.VERY_CHEAP = counts.VERY_CHEAP + 1 %}
{% elif p.level == 'CHEAP' %}
{% set counts.CHEAP = counts.CHEAP + 1 %}
{% elif p.level == 'NORMAL' %}
{% set counts.NORMAL = counts.NORMAL + 1 %}
{% elif p.level == 'EXPENSIVE' %}
{% set counts.EXPENSIVE = counts.EXPENSIVE + 1 %}
{% elif p.level == 'VERY_EXPENSIVE' %}
{% set counts.VERY_EXPENSIVE = counts.VERY_EXPENSIVE + 1 %}
{% endif %}
{% endfor %}
{% set d = dict({
'VERY_CHEAP': counts.VERY_CHEAP,
'CHEAP': counts.CHEAP,
'NORMAL': counts.NORMAL,
'EXPENSIVE': counts.EXPENSIVE,
'VERY_EXPENSIVE': counts.VERY_EXPENSIVE
}) %}
{{ d }}
{% else %}
unavailable
{% endif %}
min: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{{ entry.level }}
{% else %}
unavailable
{% endif %}
min_friendly_name: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{% if entry.level == 'VERY_CHEAP' %}
Sehr günstig
{% elif entry.level == 'CHEAP' %}
Günstig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'EXPENSIVE' %}
Teuer
{% elif entry.level == 'VERY_EXPENSIVE' %}
Sehr teuer
{% endif %}
{% else %}
unavailable
{% endif %}
min_begin_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | min(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, found=false) %}
{% for p in data | reverse %}
{% if p.startsAt < entry.startsAt %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else now().replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
min_end_at: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% if data_today is not none and data_today | length > 0 %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set entry = data_today | min(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(end=entry.startsAt, found=false) %}
{% for p in combined %}
{% if p.startsAt > entry.startsAt %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else now().replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
min_duration_h: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% if data_today is not none and data_today | length > 0 %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set entry = data_today | min(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, end=entry.startsAt, end_found=false) %}
{% for p in combined | reverse %}
{% if p.startsAt < entry.startsAt %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in combined %}
{% if p.startsAt > entry.startsAt %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
2_min: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | min(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | min(attribute='total') %}
{{ secondary.level }}
{% else %}
unavailable2
{% endif %}
{% else %}
unavailable1
{% endif %}
max: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{{ entry.level }}
{% else %}
unavailable
{% endif %}
max_friendly_name: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{% if entry.level == 'VERY_CHEAP' %}
Sehr günstig
{% elif entry.level == 'CHEAP' %}
Günstig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'EXPENSIVE' %}
Teuer
{% elif entry.level == 'VERY_EXPENSIVE' %}
Sehr teuer
{% endif %}
{% else %}
unavailable
{% endif %}
max_begin_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set entry = data | max(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, found=false) %}
{% for p in data | reverse %}
{% if p.startsAt < entry.startsAt %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else now().replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
max_end_at: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% if data_today is not none and data_today | length > 0 %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set entry = data_today | max(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(end=entry.startsAt, found=false) %}
{% for p in combined %}
{% if p.startsAt > entry.startsAt %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else now().replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
max_duration_h: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% if data_today is not none and data_today | length > 0 %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set entry = data_today | max(attribute='total') %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, end=entry.startsAt, end_found=false) %}
{% for p in combined | reverse %}
{% if p.startsAt < entry.startsAt %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in combined %}
{% if p.startsAt > entry.startsAt %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
2_max: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{# 1. Günstigsten Eintrag finden #}
{% set primary = data | max(attribute='total') %}
{% set primary_level = primary.level %}
{% set ns1 = namespace(begin=primary.startsAt, end=primary.startsAt) %}
{# 2. Begin-Ende des ersten Slots ermitteln #}
{% for p in data | reverse %}
{% if p.startsAt < primary.startsAt and p.level == primary_level %}
{% set ns1.begin = p.startsAt %}
{% elif p.startsAt < primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{% for p in data %}
{% if p.startsAt > primary.startsAt and p.level == primary_level %}
{% set ns1.end = p.startsAt %}
{% elif p.startsAt > primary.startsAt %}
{% break %}
{% endif %}
{% endfor %}
{# 3. Bereich ausschließen #}
{% set reduced = namespace(list=[]) %}
{% for p in data %}
{% if as_timestamp(p.startsAt) < as_timestamp(ns1.begin) or as_timestamp(p.startsAt) > as_timestamp(ns1.end) %}
{% set reduced.list = reduced.list + [p] %}
{% endif %}
{% endfor %}
{# 4. Zweitgünstigsten Eintrag suchen #}
{% if reduced.list | length > 0 %}
{% set secondary = reduced.list | max(attribute='total') %}
{{ secondary.level }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Rating Today"
unique_id: tibber_price_rating_today
device_class: enum
state: >
{% set rating = (state_attr('sensor.tibber_price_data_pricerating_daily', 'priceRating') or {}).get('daily', {}).get('entries', []) %}
{% if rating | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT00:00:00') %}
{% set current = rating | selectattr('time', 'search', today) | list | first %}
{{ current.level if current is defined else 'unavailable' }}
{% else %}
unavailable
{% endif %}
icon: >
{% set rating = (state_attr('sensor.tibber_price_data_pricerating_daily', 'priceRating') or {}).get('daily', {}).get('entries', []) %}
{% if rating | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT00:00:00') %}
{% set current = rating | selectattr('time', 'search', today) | list | first %}
{% if current is not defined %}
mdi:help-circle
{% elif current.level == 'LOW' %}
mdi:cash-check
{% elif current.level == 'NORMAL' %}
mdi:cash
{% elif current.level == 'HIGH' %}
mdi:cash-remove
{% endif %}
{% else %}
unavailable
{% endif %}
attributes:
friendly_name: "Dominante Preisphase heute"
description: "Classifies the electricity price compared to other hours of the last 24 hours."
description_de: "Einordnung des Strompreises im Vergleich zu anderen Stunden der letzten 24 Stunden."
valid_states: '["LOW", "NORMAL", "HIGH"]'
friendly_state: >
{% set rating = (state_attr('sensor.tibber_price_data_pricerating_daily', 'priceRating') or {}).get('daily', {}).get('entries', []) %}
{% if rating | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT00:00:00') %}
{% set current = rating | selectattr('time', 'search', today) | list | first %}
{% if current is not defined %}
unavailable
{% elif current.level == 'LOW' %}
Niedrig
{% elif current.level == 'NORMAL' %}
Normal
{% elif current.level == 'HIGH' %}
Hoch
{% endif %}
{% else %}
unavailable
{% endif %}
min: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{{ entry.level }}
{% else %}
unavailable
{% endif %}
min_friendly_name: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{% if entry.level == 'LOW' %}
Niedrig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'HIGH' %}
Hoch
{% endif %}
{% else %}
unavailable
{% endif %}
min_difference: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{{ entry.difference }}
{% else %}
unavailable
{% endif %}
min_begin_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else now().replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
min_end_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(end=entry_time.isoformat(), found=false) %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else now().replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
min_duration_h: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | min(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), end=entry_time.isoformat(), end_found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
max: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{{ entry.level }}
{% else %}
unavailable
{% endif %}
max_friendly_name: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{% if entry.level == 'LOW' %}
Niedrig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'HIGH' %}
Hoch
{% endif %}
{% else %}
unavailable
{% endif %}
max_difference: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{{ entry.difference }}
{% else %}
unavailable
{% endif %}
max_begin_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else now().replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
max_end_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(end=entry_time.isoformat(), found=false) %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else now().replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
max_duration_h: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set today = now().strftime('%Y-%m-%dT') %}
{% set entry = data | selectattr('time', 'search', today) | max(attribute='total') %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), end=entry_time.isoformat(), end_found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
- trigger:
- trigger: time_pattern
minutes: 0
- trigger: state
entity_id:
- sensor.tibber_price_data_priceinfo
- sensor.tibber_price_data_pricerating_hourly
sensor:
- name: "Tibber Price Current Hour"
unique_id: tibber_price_current_hour
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('startsAt', 'search', now_hour) | list | first %}
{{ entry.total * 100 if entry is defined else 'unavailable' }}
icon: mdi:cash-clock
attributes:
friendly_name: "Strompreis aktuell"
description: "Price in the current hour."
description_de: "Strompreis in der aktuellen Stunde."
start_at: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('startsAt', 'search', now_hour) | list | first %}
{{ entry.startsAt if entry is defined else 'unavailable' }}
energy: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('startsAt', 'search', now_hour) | list | first %}
{{ entry.energy * 100 if entry is defined else 'unavailable' }}
tax: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('startsAt', 'search', now_hour) | list | first %}
{{ entry.tax * 100 if entry is defined else 'unavailable' }}
difference_to_before: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set this_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set last_hour = (now() + timedelta(hours=-1)).strftime('%Y-%m-%dT%H:00') %}
{% set now_entry = data | selectattr('startsAt', 'search', this_hour) | list | first %}
{% set last_entry = data | selectattr('startsAt', 'search', last_hour) | list | first %}
{% if now_entry is defined and last_entry is defined %}
{{ (now_entry.total - last_entry.total) * 100 }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
is_cheaper: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set this_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set last_hour = (now() + timedelta(hours=-1)).strftime('%Y-%m-%dT%H:00') %}
{% set now_entry = data | selectattr('startsAt', 'search', this_hour) | list | first %}
{% set last_entry = data | selectattr('startsAt', 'search', last_hour) | list | first %}
{% if now_entry is defined and last_entry is defined %}
{{ now_entry.total < last_entry.total }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
is_more_expensive: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set this_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set last_hour = (now() + timedelta(hours=-1)).strftime('%Y-%m-%dT%H:00') %}
{% set now_entry = data | selectattr('startsAt', 'search', this_hour) | list | first %}
{% set last_entry = data | selectattr('startsAt', 'search', last_hour) | list | first %}
{% if now_entry is defined and last_entry is defined %}
{{ now_entry.total > last_entry.total }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Next Hour"
unique_id: tibber_price_next_hour
device_class: monetary
unit_of_measurement: ct/kWh
state_class: total
state: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{{ entry.total * 100 if entry is defined else 'unavailable' }}
icon: mdi:cash-clock
attributes:
friendly_name: "Strompreis nächste Stunde"
description: "Price in the next hour."
description_de: "Strompreis in der nächsten Stunde."
start_at: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{{ entry.startsAt if entry is defined else 'unavailable' }}
energy: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{{ entry.energy * 100 if entry is defined else 'unavailable' }}
tax: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{{ entry.tax * 100 if entry is defined else 'unavailable' }}
difference_to_now: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set this_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set now_entry = combined | selectattr('startsAt', 'search', this_hour) | list | first %}
{% set next_entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if now_entry is defined and next_entry is defined %}
{{ (next_entry.total - now_entry.total) * 100 }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
is_cheaper: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set this_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set now_entry = combined | selectattr('startsAt', 'search', this_hour) | list | first %}
{% set next_entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if now_entry is defined and next_entry is defined %}
{{ next_entry.total < now_entry.total }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Level Current Hour"
unique_id: tibber_price_level_current_hour
state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr("startsAt", "search", now_hour) | list | first %}
{{ entry.level if entry is defined else none }}
{% else %}
unavailable
{% endif %}
icon: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr("startsAt", "search", now_hour) | list | first %}
{% if entry.level == 'VERY_CHEAP' %}
mdi:arrow-bottom-right-thick
{% elif entry.level == 'CHEAP' %}
mdi:trending-down
{% elif entry.level == 'NORMAL' %}
mdi:plus-minus-variant
{% elif entry.level == 'EXPENSIVE' %}
mdi:trending-up
{% elif entry.level == 'VERY_EXPENSIVE' %}
mdi:arrow-top-right-thick
{% else %}
mdi:help-circle
{% endif %}
{% else %}
mdi:help-circle
{% endif %}
attributes:
friendly_name: "Preisniveau aktuell (Langzeit)"
description: "Long-term level of current hour's price (compared to last 7 days)."
description_de: "Langfristige Einordnung des aktuellen Preises (7-Tage-Vergleich)."
valid_states: '["VERY_CHEAP", "CHEAP", "NORMAL", "EXPENSIVE", "VERY_EXPENSIVE"]'
friendly_state: >
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr("startsAt", "search", now_hour) | list | first %}
{% if entry.level == 'VERY_CHEAP' %}
Sehr günstig
{% elif entry.level == 'CHEAP' %}
Günstig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'EXPENSIVE' %}
Teuer
{% elif entry.level == 'VERY_EXPENSIVE' %}
Sehr teuer
{% endif %}
{% else %}
unavailable
{% endif %}
begin_at: >-
{% set data = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('startsAt', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt) %}
{% for p in data | reverse %}
{% if p.startsAt < now_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
end_at: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(end=entry.startsAt) %}
{% for p in combined %}
{% if p.startsAt > now_hour %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
duration_h: >-
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, end=entry.startsAt) %}
{% for p in combined | reverse %}
{% if p.startsAt < now_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in combined %}
{% if p.startsAt > now_hour %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
progress_h: >-
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt) %}
{% for p in combined | reverse %}
{% if p.startsAt < now_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(now()) - as_timestamp(ns.begin)) / 3600) | round(0, 'floor') }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Level Next Hour"
unique_id: tibber_price_level_next_hour
state: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{{ entry.level if entry is defined else none }}
{% else %}
unavailable
{% endif %}
icon: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr("startsAt", "search", next_hour) | list | first %}
{% if entry.level == 'VERY_CHEAP' %}
mdi:arrow-bottom-right-thick
{% elif entry.level == 'CHEAP' %}
mdi:trending-down
{% elif entry.level == 'NORMAL' %}
mdi:plus-minus-variant
{% elif entry.level == 'EXPENSIVE' %}
mdi:trending-up
{% elif entry.level == 'VERY_EXPENSIVE' %}
mdi:arrow-top-right-thick
{% else %}
mdi:help-circle
{% endif %}
{% else %}
mdi:help-circle
{% endif %}
attributes:
friendly_name: "Preisniveau nächste Stunde (Langzeit)"
description: "Long-term level of next hour's price (compared to last 7 days)."
description_de: "Langfristige Einordnung des nächsten Preises (7-Tage-Vergleich)."
valid_states: '["VERY_CHEAP", "CHEAP", "NORMAL", "EXPENSIVE", "VERY_EXPENSIVE"]'
friendly_state: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr("startsAt", "search", next_hour) | list | first %}
{% if entry.level == 'VERY_CHEAP' %}
Sehr günstig
{% elif entry.level == 'CHEAP' %}
Günstig
{% elif entry.level == 'NORMAL' %}
Normal
{% elif entry.level == 'EXPENSIVE' %}
Teuer
{% elif entry.level == 'VERY_EXPENSIVE' %}
Sehr teuer
{% endif %}
{% else %}
unavailable
{% endif %}
begin_at: >-
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt) %}
{% for p in combined | reverse %}
{% if p.startsAt < next_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
end_at: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(end=entry.startsAt) %}
{% for p in combined %}
{% if p.startsAt > next_hour %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
duration_h: >-
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt, end=entry.startsAt) %}
{% for p in combined | reverse %}
{% if p.startsAt < next_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in combined %}
{% if p.startsAt > next_hour %}
{% if p.level != level %}
{% set ns.end = p.startsAt %}
{% break %}
{% else %}
{% set ns.end = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
progress_h: >-
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today %}
{% if data_tomorrow is not none and data_tomorrow | length > 0 %}
{% set combined = combined + data_tomorrow %}
{% endif %}
{% if combined is not none and combined | length > 0 %}
{% set next_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = combined | selectattr('startsAt', 'search', next_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.startsAt) %}
{% for p in combined | reverse %}
{% if p.startsAt < next_hour %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p.startsAt %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp((now() + timedelta(hours=1))) - as_timestamp(ns.begin)) / 3600) | round(0, 'floor') }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
is_cheaper: >
unknown
is_more_expensive: >
unknown
- name: "Tibber Price Level Next Change"
unique_id: tibber_price_level_next_change
device_class: timestamp
icon: >
{% set order = {
'VERY_CHEAP': 0,
'CHEAP': 1,
'NORMAL': 2,
'EXPENSIVE': 3,
'VERY_EXPENSIVE': 4
} %}
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today', []) %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow', []) %}
{% set combined = data_today + data_tomorrow if data_tomorrow else data_today %}
{% set current_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = combined | selectattr('startsAt', 'search', current_hour) | list | first %}
{% if current is defined %}
{% set current_level = order.get(current.level) %}
{% for entry in combined %}
{% if entry.startsAt > current_hour and order.get(entry.level) != current_level %}
{% set next_level = order.get(entry.level) %}
{% if next_level > current_level %}
mdi:arrow-up-bold
{% elif next_level < current_level %}
mdi:arrow-down-bold
{% else %}
mdi:minus
{% endif %}
{% break %}
{% endif %}
{% endfor %}
{% else %}
mdi:help-circle
{% endif %}
state: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today', []) %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow', []) %}
{% set combined = data_today + data_tomorrow if data_tomorrow else data_today %}
{% set current_time = now().strftime('%Y-%m-%dT%H:00') %}
{% set current_entry = combined | selectattr('startsAt', 'search', current_time) | list | first %}
{% if current_entry is defined %}
{% set current_level = current_entry.level %}
{% for entry in combined %}
{% if entry.startsAt > current_time and entry.level != current_level %}
{{ entry.startsAt }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unavailable
{% endif %}
attributes:
friendly_name: "Wechsel des Preisniveaus"
description: "Time when the next price level change occurs."
description_de: "Zeitpunkt des nächsten Wechsels im Preisniveau."
from_level: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today + data_tomorrow if data_tomorrow else data_today %}
{% set current_time = now().strftime('%Y-%m-%dT%H:00') %}
{% set current_entry = combined | selectattr('startsAt', 'search', current_time) | list | first %}
{{ current_entry.level if current_entry is defined else 'unknown' }}
to_level: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today') %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow') %}
{% set combined = data_today + data_tomorrow if data_tomorrow else data_today %}
{% set current_time = now().strftime('%Y-%m-%dT%H:00') %}
{% set current_entry = combined | selectattr('startsAt', 'search', current_time) | list | first %}
{% if current_entry is defined %}
{% set current_level = current_entry.level %}
{% for entry in combined %}
{% if entry.startsAt > current_time and entry.level != current_level %}
{{ entry.level }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unknown
{% endif %}
in_h: >
{% set data_today = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('today', []) %}
{% set data_tomorrow = (state_attr('sensor.tibber_price_data_priceinfo', 'priceInfo') or {}).get('tomorrow', []) %}
{% set combined = data_today + data_tomorrow if data_tomorrow else data_today %}
{% set current_time = now().strftime('%Y-%m-%dT%H:00') %}
{% set current_entry = combined | selectattr('startsAt', 'search', current_time) | list | first %}
{% if current_entry is defined %}
{% set current_level = current_entry.level %}
{% for entry in combined %}
{% if entry.startsAt > current_time and entry.level != current_level %}
{{ ((as_timestamp(entry.startsAt) - as_timestamp(now())) / 3600) | round(0, 'ceil') }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Rating Current Hour"
unique_id: tibber_price_rating_current_hour
state: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{{ current.level if current is defined else 'unavailable' }}
{% else %}
unavailable
{% endif %}
icon: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is not defined %}
mdi:help-circle
{% elif current.level == 'LOW' %}
mdi:cash-check
{% elif current.level == 'NORMAL' %}
mdi:cash
{% elif current.level == 'HIGH' %}
mdi:cash-remove
{% endif %}
{% else %}
unavailable
{% endif %}
attributes:
friendly_name: "Preisphase aktuell"
description: "Classifies the electricity price compared to other hours of the last 24 hours."
description_de: "Einordnung des Strompreises im Vergleich zu anderen Stunden der letzten 24 Stunden."
valid_states: '["LOW", "NORMAL", "HIGH"]'
friendly_state: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is not defined %}
unavailable
{% elif current.level == 'LOW' %}
Niedrig
{% elif current.level == 'NORMAL' %}
Normal
{% elif current.level == 'HIGH' %}
Hoch
{% endif %}
{% else %}
unavailable
{% endif %}
begin_at: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else now().replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
end_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(end=entry_time.isoformat(), found=false) %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else now().replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
duration_h: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), end=entry_time.isoformat(), end_found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
(now() + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
progress_h: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set entry_time = as_datetime(entry.time) %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.time) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(now()) - as_timestamp(ns.begin)) / 3600) | round(0, 'floor') }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Rating Next Hour"
unique_id: tibber_price_rating_next_hour
state: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{{ current.level if current is defined else 'unavailable' }}
{% else %}
unavailable
{% endif %}
icon: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is not defined %}
mdi:help-circle
{% elif current.level == 'LOW' %}
mdi:cash-check
{% elif current.level == 'NORMAL' %}
mdi:cash
{% elif current.level == 'HIGH' %}
mdi:cash-remove
{% endif %}
{% else %}
unavailable
{% endif %}
attributes:
friendly_name: "Preisphase nächste Stunde"
description: "Classifies the electricity price compared to other hours of the last 24 hours."
description_de: "Einordnung des Strompreises im Vergleich zu anderen Stunden der letzten 24 Stunden."
valid_states: '["LOW", "NORMAL", "HIGH"]'
friendly_state: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is not defined %}
unavailable
{% elif current.level == 'LOW' %}
Niedrig
{% elif current.level == 'NORMAL' %}
Normal
{% elif current.level == 'HIGH' %}
Hoch
{% endif %}
{% else %}
unavailable
{% endif %}
begin_at: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% if entry is defined %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.begin if ns.found else (now() + timedelta(hours=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat() }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
end_at: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(end=entry_time.isoformat(), found=false) %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.end if ns.found else (
(now() + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
if data_tomorrow is not none and data_tomorrow | length > 0
else (now() + timedelta(hours=1)).replace(hour=23, minute=59, second=59, microsecond=0).isoformat()
) }}
{% else %}
unavailable
{% endif %}
duration_h: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set level = entry.level %}
{% set entry_time = as_datetime(entry.time) %}
{% set ns = namespace(begin=entry_time.isoformat(), end=entry_time.isoformat(), end_found=false) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{% for p in data %}
{% set p_time = as_datetime(p.time) %}
{% if p_time > entry_time %}
{% if p.level != level %}
{% set ns.end = p_time.isoformat() %}
{% set ns.end_found = true %}
{% break %}
{% else %}
{% set ns.end = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp(ns.end if ns.end_found else (
((now() + timedelta(hours=1)) + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0).isoformat()
)) - as_timestamp(ns.begin)) / 3600) | int }}
{% else %}
unavailable
{% endif %}
progress_h: >-
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data is not none and data | length > 0 %}
{% set now_hour = (now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:00') %}
{% set entry = data | selectattr('time', 'search', now_hour) | list | first %}
{% set entry_time = as_datetime(entry.time) %}
{% if entry is defined %}
{% set level = entry.level %}
{% set ns = namespace(begin=entry.time) %}
{% for p in data | reverse %}
{% set p_time = as_datetime(p.time) %}
{% if p_time < entry_time %}
{% if p.level != level %}
{% break %}
{% else %}
{% set ns.begin = p_time.isoformat() %}
{% endif %}
{% endif %}
{% endfor %}
{{ ((as_timestamp((now() + timedelta(hours=1))) - as_timestamp(ns.begin)) / 3600) | round(0, 'floor') }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
- name: "Tibber Price Rating Next Change"
unique_id: tibber_price_rating_next_change
device_class: timestamp
state: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is defined %}
{% set current_level = current.level %}
{% for entry in data %}
{% if entry.time > now_hour and entry.level != current_level %}
{{ entry.time }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
icon: >
{% set order = {'LOW': 0, 'NORMAL': 1, 'HIGH': 2} %}
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is defined %}
{% set current_level = order.get(current.level) %}
{% for entry in data %}
{% if entry.time > now_hour and order.get(entry.level) != current_level %}
{% set next_level = order.get(entry.level) %}
{% if next_level > current_level %}
mdi:arrow-up-bold
{% elif next_level < current_level %}
mdi:arrow-down-bold
{% else %}
mdi:minus
{% endif %}
{% break %}
{% endif %}
{% endfor %}
{% else %}
mdi:help-circle
{% endif %}
{% else %}
mdi:help-circle
{% endif %}
attributes:
friendly_name: "Nächste Preisphase"
description: "Time when the next price phase of the day begins."
description_de: "Zeitpunkt der nächsten Preisphase des Tages."
from_rating: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{{ current.level if current is defined else 'unknown' }}
{% else %}
unknown
{% endif %}
to_rating: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is defined %}
{% set current_level = current.level %}
{% for entry in data %}
{% if entry.time > now_hour and entry.level != current_level %}
{{ entry.level }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unknown
{% endif %}
{% else %}
unknown
{% endif %}
in_h: >
{% set data = (state_attr('sensor.tibber_price_data_pricerating_hourly', 'priceRating') or {}).get('hourly', {}).get('entries', []) %}
{% if data | length > 0 %}
{% set now_hour = now().strftime('%Y-%m-%dT%H:00') %}
{% set current = data | selectattr('time', 'search', now_hour) | list | first %}
{% if current is defined %}
{% set current_level = current.level %}
{% for entry in data %}
{% if entry.time > now_hour and entry.level != current_level %}
{{ ((as_timestamp(entry.time) - as_timestamp(now())) / 3600) | round(0, 'ceil') }}
{% break %}
{% endif %}
{% endfor %}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
views:
- type: sections
max_columns: 3
title: Dynamischer Stromtarif
path: strompreis
icon: mdi:transmission-tower
subview: true
sections:
- type: grid
cards:
- type: heading
icon: mdi:numeric-1-circle-outline
heading: Aktuelle Stunde
heading_style: title
badges:
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_current_hour
icon: ''
color: amber
tap_action:
action: more-info
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_rating_current_hour
state_content: friendly_state
tap_action:
action: more-info
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_level_current_hour
tap_action:
action: more-info
state_content: friendly_state
- graph: none
type: sensor
entity: sensor.tibber_geschatzte_kosten_aktuelle_stunde
detail: 1
name: Kosten geschätzt
icon: mdi:currency-eur
hours_to_show: 1
- graph: line
type: sensor
entity: sensor.tibber_geschatzter_verbrauch_aktuelle_stunde
detail: 2
name: Verbrauch geschätzt
hours_to_show: 1
- type: heading
icon: mdi:numeric-2-circle-outline
heading: Nächste Stunde
heading_style: subtitle
badges:
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_next_hour
color: amber
tap_action:
action: more-info
icon: ''
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_rating_next_hour
tap_action:
action: more-info
state_content: friendly_state
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_level_next_hour
tap_action:
action: more-info
state_content: friendly_state
- type: glance
entities:
- entity: sensor.tibber_price_rating_next_change
- entity: sensor.tibber_price_level_next_change
- chart_type: line
period: 5minute
type: statistics-graph
entities:
- sensor.tibber_leistung
- sensor.solarflow_output_home_power
stat_types:
- mean
- max
- min
days_to_show: 0.4
hide_legend: false
logarithmic_scale: false
title: Leistungskurve
- type: grid
cards:
- type: heading
icon: mdi:hours-24
heading: Tagesübersicht
heading_style: title
badges:
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_today
tap_action:
action: more-info
icon: ''
color: amber
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_rating_today
state_content: friendly_state
tap_action:
action: more-info
- type: entity
show_state: true
show_icon: true
entity: sensor.tibber_price_level_today
state_content: friendly_state
tap_action:
action: more-info
- graph: none
type: sensor
entity: sensor.tibber_kumulierte_kosten
detail: 1
name: Kosten
icon: mdi:currency-eur
- graph: line
type: sensor
entity: sensor.tibber_kumulierter_verbrauch
detail: 2
name: Verbrauch
hours_to_show: 24
- type: tile
entity: sensor.tibber_price_minimum_today
features_position: bottom
vertical: false
name: Günstigste Stunde
state_content: start_at
color: green
- type: tile
entity: sensor.tibber_price_maximum_today
features_position: bottom
vertical: false
name: Teuerste Stunde
state_content: start_at
color: red
- type: custom:apexcharts-card
update_interval: 5m
span:
start: day
header:
show: true
title: Preisphasen Tagesverlauf
show_states: false
apex_config:
stroke:
curve: stepline
fill:
opacity: 0.4
tooltip:
x:
format: HH:mm
legend:
show: true
yaxis:
- id: price
decimals: 0
min: 10
- id: pv
decimals: 0
min: 0
max: 800
opposite: true
apex_config:
labels:
style:
colors: '#0077ff'
now:
show: true
color: '#8e24aa'
label: 🕒 LIVE
all_series_config:
stroke_width: 1
show:
legend_value: false
series:
- entity: sensor.tibber_price_data_pricerating_hourly
name: Niedrig
type: area
color: '#2ecc71'
yaxis_id: price
show:
extremas: true
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === today.getTime()
);
const isNextValid = (
nextDate.getTime() === today.getTime()
);
if (isTodayOrTomorrow && p.level === 'LOW') {
points.push([ts, p.total * 100]);
if (next.level === 'LOW' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.tibber_price_data_pricerating_hourly
name: Normal
type: area
color: '#f1c40f'
yaxis_id: price
show:
extremas: false
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === today.getTime() || pDate.getTime() === tomorrow.getTime()
);
const isNextValid = (
nextDate.getTime() === today.getTime()
);
if (isTodayOrTomorrow && p.level === 'NORMAL') {
points.push([ts, p.total * 100]);
if (next.level === 'NORMAL' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.tibber_price_data_pricerating_hourly
name: Hoch
type: area
color: '#e74c3c'
yaxis_id: price
show:
extremas: true
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === today.getTime()
);
const isNextValid = (
nextDate.getTime() === today.getTime() || nextDate.getTime() === tomorrow.getTime()
);
if (isTodayOrTomorrow && p.level === 'HIGH') {
points.push([ts, p.total * 100]);
if (next.level === 'HIGH' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.durchschnitt_pv_leistung_10min
name: PV-Produktion
yaxis_id: pv
type: line
color: '#0077ff'
stroke_width: 2
- entity: sensor.energy_production_today
name: PV-Prognose
type: line
stroke_width: 1
stroke_dash: 5.5
curve: smooth
yaxis_id: pv
color: '#0077ff'
data_generator: |
const result = [];
const now = new Date();
const data = entity.attributes.watts;
for (const [timestamp, value] of Object.entries(data)) {
const ts = new Date(timestamp);
if (ts >= now) {
result.push([ts, value]);
}
}
return result;
- type: custom:apexcharts-card
update_interval: 5m
span:
start: day
header:
show: true
title: Preisniveau
show_states: false
apex_config:
stroke:
curve: stepline
fill:
opacity: 0.4
tooltip:
x:
format: HH:mm
legend:
show: true
yaxis:
- id: price
decimals: 0
min: 10
now:
show: true
color: '#8e24aa'
label: 🕒 LIVE
all_series_config:
stroke_width: 1
show:
legend_value: false
series:
- entity: sensor.tibber_price_data_priceinfo
name: Sehr günstig
type: area
color: '#27ae60'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = [...(entity.attributes.priceInfo?.today || []),
...(entity.attributes.priceInfo?.tomorrow || [])];
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'VERY_CHEAP') {
points.push([ts, p.total * 100]);
if (next.level === 'VERY_CHEAP') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'VERY_CHEAP') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Günstig
type: area
color: '#2ecc71'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = [...(entity.attributes.priceInfo?.today || []),
...(entity.attributes.priceInfo?.tomorrow || [])];
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'CHEAP') {
points.push([ts, p.total * 100]);
if (next.level === 'CHEAP') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'CHEAP') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Normal
type: area
color: '#f1c40f'
unit: ct/kWh
yaxis_id: price
show:
extremas: false
data_generator: >
const data = [...(entity.attributes.priceInfo?.today || []),
...(entity.attributes.priceInfo?.tomorrow || [])];
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'NORMAL') {
points.push([ts, p.total * 100]);
if (next.level === 'NORMAL') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'NORMAL') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Teuer
type: area
color: '#e67e22'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = [...(entity.attributes.priceInfo?.today || []),
...(entity.attributes.priceInfo?.tomorrow || [])];
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'EXPENSIVE') {
points.push([ts, p.total * 100]);
if (next.level === 'EXPENSIVE') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'EXPENSIVE') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Sehr teuer
type: area
color: '#e74c3c'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = [...(entity.attributes.priceInfo?.today || []),
...(entity.attributes.priceInfo?.tomorrow || [])];
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'VERY_EXPENSIVE') {
points.push([ts, p.total * 100]);
if (next.level === 'VERY_EXPENSIVE') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'VERY_EXPENSIVE') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- type: grid
cards:
- type: heading
icon: mdi:calendar-plus
heading: Vorschau auf morgen
heading_style: title
badges: []
visibility:
- condition: state
entity: binary_sensor.tibber_price_tomorrow_available
state: 'on'
- type: tile
entity: sensor.tibber_price_tomorrow
features_position: bottom
vertical: true
grid_options:
rows: 1
columns: full
hide_state: false
name: Preis Ø
color: amber
- type: tile
entity: sensor.tibber_price_minimum_tomorrow
features_position: bottom
vertical: false
name: Günstigste Stunde
state_content: start_at
color: green
- type: tile
entity: sensor.tibber_price_maximum_tomorrow
features_position: bottom
vertical: false
name: Teuerste Stunde
state_content: start_at
color: red
- type: custom:apexcharts-card
update_interval: 5m
graph_span: 24h
span:
start: day
offset: +1d
header:
show: true
title: Preisphasen Tagesverlauf
show_states: false
apex_config:
stroke:
curve: stepline
fill:
opacity: 0.4
tooltip:
x:
format: HH:mm
legend:
show: true
yaxis:
- id: price
decimals: 0
min: 10
- id: pv
decimals: 0
min: 0
max: 800
opposite: true
apex_config:
labels:
style:
colors: '#0077ff'
all_series_config:
stroke_width: 1
show:
legend_value: false
series:
- entity: sensor.tibber_price_data_pricerating_hourly
name: Niedrig
type: area
color: '#2ecc71'
yaxis_id: price
show:
extremas: true
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === tomorrow.getTime()
);
const isNextValid = (
nextDate.getTime() === tomorrow.getTime()
);
if (isTodayOrTomorrow && p.level === 'LOW') {
points.push([ts, p.total * 100]);
if (next.level === 'LOW' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.tibber_price_data_pricerating_hourly
name: Normal
type: area
color: '#f1c40f'
yaxis_id: price
show:
extremas: false
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === tomorrow.getTime()
);
const isNextValid = (
nextDate.getTime() === tomorrow.getTime()
);
if (isTodayOrTomorrow && p.level === 'NORMAL') {
points.push([ts, p.total * 100]);
if (next.level === 'NORMAL' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.tibber_price_data_pricerating_hourly
name: Hoch
type: area
color: '#e74c3c'
yaxis_id: price
show:
extremas: true
data_generator: >
const entries = entity.attributes.priceRating?.hourly?.entries
|| [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getTime() + 86400000);
const points = [];
for (let i = 0; i < entries.length - 1; i++) {
const p = entries[i];
const next = entries[i + 1];
const pDate = new Date(p.time);
pDate.setHours(0, 0, 0, 0);
const nextDate = new Date(next.time);
nextDate.setHours(0, 0, 0, 0);
const ts = new Date(p.time).getTime();
const tsNext = new Date(next.time).getTime();
const isTodayOrTomorrow = (
pDate.getTime() === tomorrow.getTime()
);
const isNextValid = (
nextDate.getTime() === today.getTime() || nextDate.getTime() === tomorrow.getTime()
);
if (isTodayOrTomorrow && p.level === 'HIGH') {
points.push([ts, p.total * 100]);
if (next.level === 'HIGH' && isNextValid) {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
return points;
- entity: sensor.energy_production_tomorrow
name: PV-Prognose
type: line
stroke_width: 2
stroke_dash: 5.5
curve: smooth
yaxis_id: pv
color: '#0077ff'
data_generator: >
return Object.entries(entity.attributes.watts).map(([ts, val])
=> [ts, val]);
- type: custom:apexcharts-card
update_interval: 5m
graph_span: 24h
span:
start: day
offset: +1d
header:
show: true
title: Preisniveau
show_states: false
apex_config:
stroke:
curve: stepline
fill:
opacity: 0.4
tooltip:
x:
format: HH:mm
legend:
show: true
yaxis:
- id: price
decimals: 0
min: 10
all_series_config:
stroke_width: 1
show:
legend_value: false
series:
- entity: sensor.tibber_price_data_priceinfo
name: Sehr günstig
type: area
color: '#27ae60'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = entity.attributes.priceInfo?.tomorrow; const
points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'VERY_CHEAP') {
points.push([ts, p.total * 100]);
if (next.level === 'VERY_CHEAP') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'VERY_CHEAP') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Günstig
type: area
color: '#2ecc71'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: |
const data = entity.attributes.priceInfo?.tomorrow;
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'CHEAP') {
points.push([ts, p.total * 100]);
if (next.level === 'CHEAP') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'CHEAP') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Normal
type: area
color: '#f1c40f'
unit: ct/kWh
yaxis_id: price
show:
extremas: false
data_generator: |
const data = entity.attributes.priceInfo?.tomorrow;
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'NORMAL') {
points.push([ts, p.total * 100]);
if (next.level === 'NORMAL') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'NORMAL') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Teuer
type: area
color: '#e67e22'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: >
const data = entity.attributes.priceInfo?.tomorrow; const
points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'EXPENSIVE') {
points.push([ts, p.total * 100]);
if (next.level === 'EXPENSIVE') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'EXPENSIVE') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
- entity: sensor.tibber_price_data_priceinfo
name: Sehr teuer
type: area
color: '#e74c3c'
unit: ct/kWh
yaxis_id: price
show:
extremas: true
data_generator: |
const data = entity.attributes.priceInfo?.tomorrow;
const points = [];
for (let i = 0; i < data.length - 1; i++) {
const p = data[i], next = data[i + 1];
const ts = new Date(p.startsAt).getTime(), tsNext = new Date(next.startsAt).getTime();
if (p.level === 'VERY_EXPENSIVE') {
points.push([ts, p.total * 100]);
if (next.level === 'VERY_EXPENSIVE') {
points.push([tsNext, next.total * 100]);
} else {
points.push([tsNext, p.total * 100]);
points.push([tsNext, null]);
}
}
}
const last = data[data.length - 1];
if (last?.level === 'VERY_EXPENSIVE') {
const ts = new Date(last.startsAt).getTime();
points.push([ts, last.total * 100]);
points.push([ts + 3600000, last.total * 100]);
}
return points;
visibility:
- condition: state
entity: binary_sensor.tibber_price_tomorrow_available
state: 'on'
- type: grid
cards:
- type: heading
icon: mdi:calendar-month
heading: Monatsübersicht
heading_style: title
- graph: none
type: sensor
entity: sensor.tibber_monatliche_kosten
detail: 1
name: Kosten
icon: mdi:currency-eur
hours_to_show: 168
- graph: line
type: sensor
entity: sensor.tibber_monatlicher_nettoverbrauch
detail: 1
name: Verbrauch
hours_to_show: 168
- chart_type: line
period: hour
type: statistics-graph
entities:
- sensor.tibber_leistung
- sensor.solarflow_output_home_power
stat_types:
- max
- mean
- min
days_to_show: 30
hide_legend: false
logarithmic_scale: false
title: Leistungskurve
grid_options:
columns: full
rows: auto
column_span: 3
header:
card:
type: markdown
text_only: true
content: >-
# Dynamischer Stromtarif
<u>Hinweis:</u> Kosten enthalten keine Grundgebühren, siehe [Tibber
FAQ](https://support.tibber.com/de/articles/4895076-strompreise-in-der-tibber-app).
badges:
- type: entity
show_name: true
show_state: true
show_icon: true
entity: sensor.tibber_leistung
name: Netzbezug
visibility:
- condition: or
conditions:
- condition: numeric_state
entity: sensor.tibber_leistung
above: 0
- condition: numeric_state
entity: sensor.tibber_stromerzeugung
below: 1
icon: mdi:transmission-tower-export
color: red
- type: entity
show_name: true
show_state: true
show_icon: true
entity: sensor.tibber_einspeiseleistung
name: Einspeisung
visibility:
- condition: numeric_state
entity: sensor.tibber_einspeiseleistung
above: 0
icon: mdi:transmission-tower-import
color: green
- type: entity
show_name: true
show_state: true
show_icon: true
entity: sensor.tibber_zahlerstand_verbrauch
name: Zählerstand
icon: mdi:counter
color: deep-orange
cards: []
top_margin: false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment