Appreciating you sharing your code. Trying to wrap my head around how to visualize this in the GUI. Any ideas how to make a diagram(time/price) or something similar for the 3 periods?
No, not sure about this. HA is not good at visualizing data for future…
I did this for displaying some information in the GUI:
Badge code:
badges:
- entity: binary_sensor.nordpool_kwh_cheapest_4_hours
card_mod: null
style: |
@keyframes pulse { 70% { opacity: 0; } }
:host {
--card-mod-icon-color: yellow;
animation: pulse 3s infinite;
display:
{% if is_state('binary_sensor.nordpool_kwh_cheapest_4_hours', 'off') %}
none;
{% endif %}
}
Sensor code:
- platform: template
sensors:
nordpool_kwh_cheapest_4_hours:
friendly_name: Strømpris
icon_template: >-
mdi:lightning-bolt
value_template: >-
{% set l=state_attr('sensor.nordpool_kwh_oslo', 'raw_today')|sort(attribute='value') %}
{{ (now() >= l[0].start and now() <= l[0].end)
or (now() >= l[1].start and now() <= l[1].end)
or (now() >= l[2].start and now() <= l[2].end)
or (now() >= l[3].start and now() <= l[3].end) }}
attribute_templates:
cost1: >-
{% set l=state_attr('sensor.nordpool_kwh_oslo', 'raw_today')|sort(attribute='value') %}
{{l[0].value}} / {{l[0].start.strftime("%H:%M")}} - {{ l[0].end.strftime("%H:%M")}}
cost2: >-
{% set l=state_attr('sensor.nordpool_kwh_oslo', 'raw_today')|sort(attribute='value') %}
{{l[1].value}} / {{l[1].start.strftime("%H:%M")}} - {{ l[1].end.strftime("%H:%M")}}
cost3: >-
{% set l=state_attr('sensor.nordpool_kwh_oslo', 'raw_today')|sort(attribute='value') %}
{{l[2].value}} / {{l[2].start.strftime("%H:%M")}} - {{ l[2].end.strftime("%H:%M")}}
cost4: >-
{% set l=state_attr('sensor.nordpool_kwh_oslo', 'raw_today')|sort(attribute='value') %}
{{l[3].value}} / {{l[3].start.strftime("%H:%M")}} - {{ l[3].end.strftime("%H:%M")}}
The sensor status for displaying in the GUI is “on” (not “off”, - but had to get it to show in the GUI for gif making)
If anybody is interested in native HA solution using rest api, tibber sensor and apex charts, below is the code:
REST Sensor:
You need to replace [YOUR AUTH CODE FROM TIBBER DEVELOPER PROFILE] with your access token from tibber dev profile
- platform: rest
name: Tibber prices
resource: https://api.tibber.com/v1-beta/gql
method: POST
scan_interval: 60
payload: '{ "query": "{ viewer { homes { currentSubscription { priceInfo { today { total startsAt } tomorrow { total startsAt }}}}}}" }'
json_attributes_path: "$.data.viewer.homes[0].currentSubscription.priceInfo"
json_attributes:
- today
- tomorrow
value_template: Ok
headers:
Authorization: "**[YOUR AUTH CODE FROM TIBBER DEVELOPER PROFILE]**"
Content-Type: application/json
User-Agent: REST
Apex charts YAML
type: custom:apexcharts-card
apex_config:
chart:
height: 500px
header:
show: true
title: Energy stats
show_states: true
colorize_states: true
now:
show: true
color: white
label: NOW
hours_12: false
graph_span: 36h
span:
start: day
yaxis:
- id: kWh
decimals: 0
opposite: true
max: 500
apex_config:
tickAmount: 4
- id: EUR
series:
- entity: sensor.**[YOUR TIBBER ENERGY USAGE SENSOR]**
type: column
show:
extremas: true
name: Usage
stroke_width: 2
color: '#64511c'
opacity: 0.3
yaxis_id: kWh
group_by:
func: avg
duration: 15min
- entity: sensor.tibber_prices
stroke_width: 2
color: blue
curve: smooth
show:
legend_value: false
extremas: true
in_header: false
extend_to: now
yaxis_id: EUR
name: Predicted all
data_generator: |
return entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
- entity: sensor.**[YOUR TIBBER PRICE SENSOR]**
extend_to: now
show:
extremas: true
color: pink
stroke_width: 5
yaxis_id: EUR
name: price
- entity: sensor.**[YOUR TIBBER PRICE SENSOR]**
stroke_width: 2
show:
legend_value: false
extremas: true
curve: smooth
attribute: min_price
name: MIN
color: green
yaxis_id: EUR
type: line
group_by:
duration: 24hours
func: min
- entity: sensor.**[YOUR TIBBER PRICE SENSOR]**
stroke_width: 2
yaxis_id: EUR
name: AVG
attribute: avg_price
color: violet
curve: smooth
type: line
show:
legend_value: false
extremas: true
group_by:
duration: 24hours
func: avg
- entity: sensor.**[YOUR TIBBER PRICE SENSOR]**
attribute: max_price
stroke_width: 2
curve: smooth
show:
legend_value: false
extremas: true
name: MAX
color: red
yaxis_id: EUR
type: line
group_by:
duration: 24hours
func: max
You need to replace following sensors above:
- [YOUR TIBBER ENERGY USAGE SENSOR] - any sensor really showing your consumption
- [YOUR TIBBER PRICE SENSOR] - regular tibber sensor giving you the price (with mix, max, avg attributes)
Nice work! But mine only shows past prices not up coming. Any idea why?
It’s javascript code in data_generator that has not reachable code in it. It returns before it hits tomorrow data.
Try this:
data_generator: |
var a = entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
var b= entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return a.concat(b);
I like this solution. How would one make a similar entity that gives the average price and/or Future_Price_Level for the following hour, 3 hours, and 6 hours?
Price_Level is a Tibber value based on the trailing price average over the past 3 days but Future_Price_level should be the time price average of the time period (next 1, 3, or 6 hours) in comparison with the current day’s price (or following 24-hour average price if after 14:00).
The hope with this is to be able to make more intelligent heating automation that will produce heat based on the future price rather than relying only on the current price and Tibber’s Price_Level.
Cool!!!
This rest sensor worked as a charm.
Thank you very much for sharing.
I’m also interested on future price statistics to schedule my dishwasher, so I will invest some time on this and will share here when I get something.
This is working very well!
Thanks for sharing!
If you use Node-Red I can also suggest looking at this: https://powersaver.no/
I have recently started using this for my floor thermostats and waterheater and I can confirm it works very well. This can use data from Nordpool and Tibber. Just a suggestion
Also on the future pricing of power I have installed apexcharts from HACS (https://github.com/RomRider/apexcharts-card) and use this config to get the pricing for today and tomorrow:
type: custom:apexcharts-card
graph_span: 24h
experimental:
color_threshold: true
header:
title: Powerprice
show: true
apex_config:
yaxis:
min: 0
hours_12: false
span:
start: hour
offset: '-3h'
now:
show: true
label: Now
series:
- entity: sensor.nordpool_kwh_oslo_nok_2_10_025
type: column
data_generator: |
return (entity.attributes.raw_today.map((start, index) => {
return [new Date(start["start"]).getTime(), entity.attributes.raw_today[index]["value"]];
})).concat(entity.attributes.raw_tomorrow.map((start, index) => {
return [new Date(start["start"]).getTime(), entity.attributes.raw_tomorrow[index]["value"]];
}));
color_threshold:
- value: 0
color: var(--warning-color)
- value: 1
color: var(--warning-color)
- value: 3
color: var(--success-color)
- value: 5
color: var(--warning-color)
When the powerprice for tomorrow is not published the graphs are just empty (like in the image above). At around 13:00 the prices gets updated and will be added to the graph. I now prefer this over Tibbers own graph as its easier to compare the price from one day to another.
I had the same issue as @WattageGuy (here) and used the code that @KalleL suggested (here), that solved the issue with missing future price graph.
I also found out that MIN, AVG and MAX price calculation didn’t work correctly, it didn’t calculated on future prices. To fix that I used the REST sensor and the data_generator that @KalleL came up with. With that you get correctly calculated values for Min, Avg, and Max prices.
My chart:
Thank you @p1ranha for this chart, it’s very useful these days!
Thank you @KalleL for fixing data generator!
My final code:
type: custom:apexcharts-card
apex_config:
chart:
height: 600px
header:
show: true
title: Tibber El-pris
show_states: true
colorize_states: true
now:
show: true
color: white
label: NU
hours_12: false
graph_span: 36h
span:
start: day
yaxis:
- id: kW
decimals: 1
opposite: true
apex_config:
tickAmount: 6
- id: EUR
series:
- entity: sensor.total_anvand_effekt_nu
show:
legend_value: false
extremas: true
stroke_width: 5
curve: smooth
color: '#64511c'
opacity: 0.8
yaxis_id: kW
extend_to: now
type: column
group_by:
func: avg
duration: 30min
- entity: sensor.tibber_prices
stroke_width: 2
color: blue
curve: stepline
show:
legend_value: false
extremas: true
in_header: false
extend_to: now
yaxis_id: EUR
name: Elpriset
data_generator: |
var a = entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
var b= entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return a.concat(b);
- entity: sensor.electricity_price_xxxxxxxx
extend_to: now
show:
legend_value: false
extremas: true
curve: stepline
color: '#ffa500'
stroke_width: 5
yaxis_id: EUR
name: Pris nu
- entity: sensor.tibber_prices
stroke_width: 2
show:
legend_value: false
extremas: true
data_generator: |
var a = entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
var b= entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return a.concat(b);
curve: smooth
attribute: min_price
name: Minsta pris
color: green
yaxis_id: EUR
type: line
group_by:
duration: 36h
func: min
- entity: sensor.tibber_prices
stroke_width: 2
show:
legend_value: false
extremas: true
data_generator: |
var a = entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
var b= entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return a.concat(b);
curve: smooth
attribute: avg_price
name: Medel pris
color: violet
yaxis_id: EUR
type: line
group_by:
duration: 36h
func: avg
- entity: sensor.tibber_prices
stroke_width: 2
show:
legend_value: false
extremas: true
data_generator: |
var a = entity.attributes.today.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
var b= entity.attributes.tomorrow.map((entry) => {
return [new Date(entry.startsAt), entry.total];
});
return a.concat(b);
curve: smooth
attribute: max_price
name: Max pris
color: red
yaxis_id: EUR
type: line
group_by:
duration: 36h
func: max
That power saver node red node looks like a great idea but I would really like to try to get HA to be able to do this by creating an entity that gives the price level over the next 1-3 hours using the tibber price_level.
One could use this to set a NOT condition to prevent changing a control if the price will be lower during the chosen period. Or use it to change a control now if the price will rise after x hours.
Wow. This works great.
How can I set current price to header? It shows now last price of the series.
See the picture:
So, I’ve finally started working on this, but I’ve started a new topic to avoid confusion:
Hello, nice work. Thanks for the code.
I tried to build it for my setting. How do you make that the blue line is not visible on the left side of “now”?
My goal is to have the past line ending at “now” and the forecast line start at “now” . Do you know if this is possible? And how?
Thanks in advance.
Just tried your rest sensor out but it conflicts with the normal Tibber integration at start up and results in my HA takes 10min or so to fully load but the Tibber integration fails to initialize which leads to the Energy dashboard not working etc.
Do you have any idea what might be causing it?
Hi, they really don’t have anything in common, so the only thing I would suggest is to reduce refresh rate and see if next day it improves:
In the rest sensor change this:
scan_interval: 60
to maybe
scan_interval: 3600
which will make 1 query every 1 hour.
Just tried that and it worked, one reboot but that’s 100% better than before
I’ve stopped using the “normal Tibber integration”, not because I have a conflict, but because that is not needed after a couple of modifications on @p1ranha’s REST sensor in order to add the current price on its state (instead of an “OK”):
sensor:
# https://community.home-assistant.io/t/tibber-sensor-for-future-price-tomorrow/253818/23
- platform: rest
name: Tibber Electricity Price
resource: https://api.tibber.com/v1-beta/gql
method: POST
scan_interval: 900
payload: '{ "query": "{ viewer { homes { currentSubscription { priceInfo { current { total currency level } today { total startsAt level } tomorrow { total startsAt level }}}}}}" }'
json_attributes_path: "$.data.viewer.homes[0].currentSubscription.priceInfo"
json_attributes:
- current
- today
- tomorrow
value_template: '{{ value_json["data"]["viewer"]["homes"][0]["currentSubscription"]["priceInfo"]["current"]["total"] }}'
headers:
Authorization: !secret tibber_api_token
Content-Type: application/json
User-Agent: REST
This doesn’t have the consumption statistics as the “official” integration and also doesn’t have the price level (for witch I have a template sensor to calculate, but also would be easy to add in this sensor if you don’t wanna have another sensor).
PS: Note I’m using 900 sec (15min) as scan interval, which is working fine for me and prevents taking too long to fetching for the new price when a new hour starts (this could be easily solved by a template sensor).
This is the other template sensor I’ve mentioned. It probably needs a review/clean up as I’m sure I’m not using half of those attributes available.
The result of this sensor is what I use for my energy dashboard, as if updates the current rate faster than the REST sensor (thanks to the longer scan interval I’ve used there):
template:
- sensor:
- name: Electricity price
unique_id: d4c04abf-c258-4f0f-a61d-cc0170225081
unit_of_measurement: "SEK/kWh"
icon: mdi:currency-usd
state: >-
{% if this.attributes.future_prices | default('unknown') in ['unknown', 'unavailable'] or (this.attributes.future_prices | default([]) | map(attribute='total') | list | count) < 1 %}
unknown
{% else %}
{{ this.attributes.future_prices | default([]) | map(attribute='total') | list | first | float(0) }}
{% endif %}
attributes:
current: >-
{% if state_attr('sensor.tibber_electricity_price', 'current') %}
{{ state_attr('sensor.tibber_electricity_price', 'current') }}
{% else %}
[ ]
{% endif %}
today: >-
{% if state_attr('sensor.tibber_electricity_price', 'today') %}
{{ state_attr('sensor.tibber_electricity_price', 'today') }}
{% else %}
[ ]
{% endif %}
tomorrow: >-
{% if state_attr('sensor.tibber_electricity_price', 'tomorrow') %}
{{ state_attr('sensor.tibber_electricity_price', 'tomorrow') }}
{% else %}
[ ]
{% endif %}
all_prices: "{{ this.attributes.today | default([]) + this.attributes.tomorrow | default([]) }}"
#today_count: "{{ this.attributes.today | default([]) | map(attribute='total') | list | count }}"
#tomorrow_count: "{{ this.attributes.tomorrow | default([]) | map(attribute='total') | list | count }}"
#all_prices_count: "{{ this.attributes.all_prices | default([]) | map(attribute='total') | list | count }}"
min_price: >-
{% if this.attributes.today | default('unknown') in ['unknown','unavailable','none'] or (this.attributes.today | default([]) | map(attribute='total') | list | count) < 1 %}
unknown
{% else %}
{{ this.attributes.today | default([0]) | map(attribute='total') | list | min | float(0) | round(4) }}
{% endif %}
max_price: >-
{% if this.attributes.today | default('unknown') in ['unknown','unavailable','none'] or (this.attributes.today | default([]) | map(attribute='total') | list | count) < 1 %}
unknown
{% else %}
{{ this.attributes.today | default([0]) | map(attribute='total') | list | max | float(0) | round(4) }}
{% endif %}
avg_price: >-
{% if this.attributes.today | default('unknown') in ['unknown','unavailable','none'] or (this.attributes.today | default([]) | map(attribute='total') | list | count) < 1 %}
unknown
{% elif (this.attributes.today | default([]) | map(attribute='total') | list | count) < 2 %}
{{ this.attributes.today | default([0]) | map(attribute='total') | list | max | float(0) | round(4) }}
{% else %}
{{ this.attributes.today | default([0]) | map(attribute='total') | list | average(0) | float(0) | round(4) }}
{% endif %}
price_level: >-
{% if (this.attributes.current | default('unknown')) in ['unknown','unavailable','none'] %}
unknown
{% else %}
{{ this.attributes.current.level | default('unknown') | replace('_', ' ') | capitalize }}
{% endif %}
price_level_1d: >-
{% set price_cur = this.state | default(0) | float(0) %}
{% set price_avg = this.attributes.avg_price | default(0) | float(0) %}
{% if price_cur == 0 or price_avg == 0 %}
unknown
{% else %}
{% set price_ratio = (price_cur / price_avg) %}
{% if price_ratio >= 1.4 %}
Very expensive
{% elif price_ratio >= 1.15 %}
Expensive
{% elif price_ratio <= 0.6 %}
Very cheap
{% elif price_ratio <= 0.9 %}
Cheap
{% else %}
Normal
{% endif %}
{% endif %}
price_level_combined: >-
{% set level1 = this.attributes.price_level_1d | default('unknown') %}
{% set level3 = this.attributes.price_level | default('unknown') %}
{% if level1 == level3 %}
{{ level1 }}
{% elif level1 in ['unknown','unavailable','none'] or level3 in ['unknown','unavailable','none'] %}
unknown
{% elif level1 == "Very cheap" %}
{{ level3 }}
{% elif level3 == "Very cheap" %}
{{ level1 }}
{% elif level1 == "Cheap" %}
{{ level3 }}
{% elif level3 == "Cheap" %}
{{ level1 }}
{% elif level1 == "Normal" %}
{{ level3 }}
{% elif level3 == "Normal" %}
{{ level1 }}
{% elif level1 == "Expensive" %}
{{ level3 }}
{% else %}
{{ level1 }}
{% endif %}
is_below_average: >-
{% if is_number(this.state) and is_number(this.attributes.avg_price) %}
{{ (this.state | float(0) < this.attributes.avg_price | float(0)) | lower }}
{% else %}
unknown
{% endif %}
is_above_average: >-
{% if is_number(this.state) and is_number(this.attributes.avg_price) %}
{{ (this.state | float(0) > this.attributes.avg_price | float(0)) | lower}}
{% else %}
unknown
{% endif %}
is_at_min: >-
{% if is_number(this.state) and is_number(this.attributes.min_price) %}
{{ (this.state | float(0) <= this.attributes.min_price | float(0)) | lower }}
{% else %}
unknown
{% endif %}
is_at_max: >-
{% if is_number(this.state) and is_number(this.attributes.max_price) %}
{{ (this.state | float(0) >= this.attributes.max_price | float(0)) | lower }}
{% else %}
unknown
{% endif %}
is_close_to_min: >-
{% if is_number(this.state) and is_number(this.attributes.min_price) %}
{{ (this.state | float(0) <= (1.15 * this.attributes.min_price | float(0))) | lower }}
{% else %}
unknown
{% endif %}
future_prices: >-
{% if (this.attributes.all_prices | default('unknown')) in ['unknown','unavailable','none'] %}
unknown
{% else %}
{{ (this.attributes.all_prices | default([])) | selectattr('startsAt', 'gt', (now() - timedelta(hours=1)) | string | replace(' ','T')) | list }}
{% endif %}
future_prices_totals: >-
{% if (this.attributes.future_prices | default('unknown')) in ['unknown','unavailable','none'] %}
unknown
{% else %}
{{ (this.attributes.future_prices | default([])) | map(attribute='total') | list }}
{% endif %}
#future_prices_count: "{{ (this.attributes.future_prices_totals | default([])) | count }}"
future_prices_min: |-
{% if this.attributes.future_prices_totals | default([0]) | count > 0 %}
{{ this.attributes.future_prices_totals | default([0]) | min | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_max: |-
{% if this.attributes.future_prices_totals | default([0]) | count > 0 %}
{{ (this.attributes.future_prices_totals | default([0])) | max | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_avg: >-
{% if this.attributes.future_prices_totals | default([0]) | count > 0 %}
{{ (this.attributes.future_prices_totals | default([0])) | average(0) | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_curr_price_level: >-
{% set price_cur = this.state | default(0) | float(0) %}
{% set price_avg = this.attributes.future_prices_avg | default(0) | float(0) %}
{% if price_cur == 0 or price_avg == 0 %}
unknown
{% else %}
{% set price_ratio = (price_cur / price_avg) %}
{% if price_ratio >= 1.4 %}
Very expensive
{% elif price_ratio >= 1.15 %}
Expensive
{% elif price_ratio <= 0.6 %}
Very cheap
{% elif price_ratio <= 0.9 %}
Cheap
{% else %}
Normal
{% endif %}
{% endif %}
future_prices_16h: >-
{% if (this.attributes.future_prices | default('unknown')) in ['unknown','unavailable','none'] %}
unknown
{% else %}
{{ (this.attributes.future_prices | default([]) | list)[0:16]}}
{% endif %}
future_prices_16h_totals: >-
{% if (this.attributes.future_prices_16h | default('unknown')) in ['unknown','unavailable','none'] %}
unknown
{% else %}
{{ (this.attributes.future_prices_16h | default([])) | map(attribute='total') | list }}
{% endif %}
#future_prices_16h_count: "{{ (this.attributes.future_prices_16h_totals | default([])) | count }}"
future_prices_16h_min: |-
{% if this.attributes.future_prices_16h_totals | default([0]) | count > 0 %}
{{ (this.attributes.future_prices_16h_totals | default([0])) | min | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_16h_max: |-
{% if this.attributes.future_prices_16h_totals | default([0]) | count > 0 %}
{{ (this.attributes.future_prices_16h_totals | default([0])) | max | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_16h_avg: >-
{% if this.attributes.future_prices_16h_totals | default([0]) | count > 0 %}
{{ (this.attributes.future_prices_16h_totals | default([0])) | average(0) | float(0) | round(4) }}
{% else %}
unknown
{% endif %}
future_prices_16h_current_price_level: >-
{% set price_cur = this.state | default(0) | float(0) %}
{% set price_avg = this.attributes.future_prices_16h_avg | default(0) | float(0) %}
{% if price_cur == 0 or price_avg == 0 %}
unknown
{% else %}
{% set price_ratio = (price_cur / price_avg) %}
{% if price_ratio >= 1.4 %}
Very expensive
{% elif price_ratio >= 1.15 %}
Expensive
{% elif price_ratio <= 0.6 %}
Very cheap
{% elif price_ratio <= 0.9 %}
Cheap
{% else %}
Normal
{% endif %}
{% endif %}
future_prices_16h_current_price_is_close_to_min: >-
{% if is_number(this.state) and is_number(this.attributes.future_prices_16h_min) %}
{{ (this.state | float(0) <= (1.15 * this.attributes.future_prices_16h_min | default(0) | float(0))) | lower }}
{% else %}
unknown
{% endif %}
future_prices_16h_current_price_is_at_min: >-
{% if this.state | default(0) | float(0) > 0 and this.attributes.future_prices_16h_min | default(0) | float(0) > 0 %}
{{ (this.state | default(0) | float(0) <= this.attributes.future_prices_16h_min | default(0) | float(0)) | lower }}
{% else %}
unknown
{% endif %}