Tibber sensor for future price (tomorrow)

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 :slight_smile:

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.

2 Likes

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

6 Likes

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:
image

So, I’ve finally started working on this, but I’ve started a new topic to avoid confusion:

Tibber future (tomorrow) prices statistics - Share your Projects! - Home Assistant Community (home-assistant.io)

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.

1 Like

Just tried that and it worked, one reboot but that’s 100% better than before :slight_smile:

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 %}
3 Likes

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.

3 Likes

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.
.

1 Like