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 %}
What is the sensor : sensor.electricity_price_xxxxxxxx ??
What about a solution such as this custom integration for Frank Energie is doing? They added future prices as an attribute on the sensor with the current price:
state_class: measurement
prices:
- from: '2022-12-03T00:00:00+01:00'
till: '2022-12-03T01:00:00+01:00'
price: 0.4884
- from: '2022-12-03T01:00:00+01:00'
till: '2022-12-03T02:00:00+01:00'
price: 0.4481
[...snip...]
- from: '2022-12-03T23:00:00+01:00'
till: '2022-12-04T00:00:00+01:00'
price: 0.423
unit_of_measurement: €/kWh
attribution: Data provided by Frank Energie
device_class: monetary
icon: mdi:currency-eur
friendly_name: Current electricity price (All-in)
I really like this approach as it allows me to easily use these values in an automation, e.g. to schedule my dishwasher:
alias: Dishwasher - Auto schedule Eco50
description:
Automatically schedules the Eco50 program during cheap electricity
prices when the dishwasher door is closed and remote start is enabled.
trigger:
- type: not_opened
platform: device
device_id: "{{ dishwasher_id }}"
entity_id: binary_sensor.dishwasher_door
domain: binary_sensor
condition:
- condition: state
entity_id: sensor.dishwasher_operation_state
state: Ready
- type: is_on
condition: device
device_id: "{{ dishwasher_id }}"
entity_id: binary_sensor.dishwasher_remote_control
domain: binary_sensor
action:
- service: home_connect.start_program
data:
device_id: "{{ dishwasher_id }}"
program: Dishcare.Dishwasher.Program.Eco50
key: BSH.Common.Option.StartInRelative
value: |
{% set lowest_price = state_attr('sensor.current_electricity_price_all_in', 'prices') | selectattr('from', 'gt', now()) | selectattr('till', 'lt', now() + timedelta(hours=check_prices_hours_ahead)) | min(attribute='price') %}
{% set delay = (lowest_price['from'] - now()).total_seconds() | int - delay_offset_seconds %}
{{ delay if delay > 0 else 0 }}
unit: seconds
variables:
dishwasher_id: 5ea51af1fee5c7e8f4c1b7a8ee70b048
delay_offset_seconds: 900 # takes over 15 minutes to use full power
check_prices_hours_ahead: 9
mode: single
From what I can tell (though I may be wrong!), this is not possible with the current custom component as it only provides a graph.
I just signed up for Tibber and am getting ready to make the most of it. I had the other custom component running (that showed OK in it’s standard state) and am now trying to get yours working, but instead of showing OK it actually comes up empty.
Not sure what-äs going on…I don’t get any errors, but it doesn’t appear to be getting any data from the Tibber API despite the API key…
I’ve been in so many discussions around this Tibber data and have worked so much in my sensors that I don’t know which one you are testing right now, but will spend some time later this week to read back this thread and try to understand the issue you have,
Anyway, you can find in GitHub the Tibber library I have on production on my Home Assistant right now:
This custom component provides future prices: Add hourly price data by Knodd · Pull Request #34 · Danielhiversen/home_assistant_tibber_data · GitHub
It will be great to have this from the official integration, however I could see this was merged back in October and still not available in production.
Do you have any clue in how is this process?
Aha!
I just realized that is a different repository…
I will try out later.
By the way, I liked the experimental sensors:
Experimental and requires additional configuration:
- Grid price (Only if your grid company is supported by Tibber)
- Estimated total price with subsidy and grid price (Only if your grid company is supported by Tibber)
How can I setup these additional configuration?
Thanks for your replies. Right now I think my problwm isn’t with the custom component at all, it’s simply that Tibber hasn’t “activated” my contract yet, so I don’t get any data from the API at all. I will keep waiting ans trying and see what happens once my switch to Tibber is complete. I might have to stick with Nordpool as a data source anyway because there are some nice integrations (like the EV Smart Charge) that rely und Nordpool data.
.