Cheapest Energy Hours - Jinja macro for dynamic energy prices

UPDATE

v5.0.0

This version was first reseased as beta, and now is the time for the offical v5.0.0 release. :tada:

Version 4.2 needed quite some bug fixes, sorry about that. In the last week a made a lot of changes based on the feedback of users. So many thanks for that. Keep sending in issues and questions, it will help to improve the macro even more!

Initially I’ve created the macro with only full hours in mind. The price data I worked with (Nordpool) only had hourly data, and the macro only did not really take minutes into account for the setting for the start and end period of the prices to check. In v3.0.0 support for sensors with more than one price per hour was added, and in 4.0.0 support for decimals in the hour setting was added.
Version brings the same support to the start and end parameters. You always could enter data which wasn’t a whole hour, but now the data will be converted to actually use that input.

:rotating_light: BREAKING CHANGES

  • Refactor of look_ahead
    • The way look_ahead works has been reworked. look_ahead_minutes is no longer used, and therefor marked as depreciated in the macro. For now the parameter will be left in, so it will not break you current templates. In v5.0.0 the current time is checked, and converted to a start time shortly before it. To support this, the data in your source sensor is split into smaller parts (minimum of 5 minutes, so 12 parts per hour). So if you enable look_ahead and the current time is eg 11:06, the start time for the price selection will be 11:10 and there will be 6 data points per hour. If the current time is 12:18, the start time will be 12:20 and there will be 3 data points per hour.
    • In case mode is_now is used, the start time will always be set before the current time if it needs to be recalculated. So 13:14 will become 13:10. Otherwise is_now will never return true after recaculation of the start time.

:sparkles: IMPROVEMENTS

  • Both start, end and hours (optionally defined based on weight) will be used to split the data, start, end and hours will be adjusted accordingly. If there is a different outcome for one of these parameters, a conversion to 5 minutes (12 datapoints per hour) will be used. The no_weight_points parameter can be used to overrule this. When set the setting for no_weight_points will be used for the adjustments of start, end and hours.
  • mode='split is now a parameter split which can be set to true or false. So you can now eg use mode='is_now', split=true to immediately see if the current time is in one of the cheapest blocks. Not all modes work the same as for split=false and this is described in the DATA OUTPUT section
  • mode='split' will still work, and will give the same output as split=true, mode='all'
  • new error messages based on the start and end time are added. There will now be a clear error if the start is after the end and if the hours setting is higher than the difference between start and end
  • the error message on the number of hours needed, and the hours available has been added
  • the error message when there is not enough data is updated, and shows what’s the difference between the data needed, and the available data
  • a debug parameter is aded, if set to true it will return a dictionary with a lot of data which can be helpful for debugging, including the output based on the other parameters.
  • if no input for the sensor is provided, the macro will try to find a sensor with the attributes as set for attr_today and attr_tomorrow. So basically if you use the Nordpool integration, or an integration with the same attributes it wil give the start time of the cheapest hour today if you only set use cheapes_energy_hours(). This can be helpful if for some reason the entity_id changes. Do note that it’s most resource friendly to set the sensor data yourself if it not matches the defaults, so the macro doesn’t have to search for it (the same applies to the attributes and keys).
  • Lots of code optimization

:books: DOCUMENTATION

  • The documentation has been rewritten and a separte HOW TO section has been added.

:bug: BUG FIXES

  • lowest=false was not working in combination with split, this has been corrected.
  • the calculation for the weight input has been corrected.

Again, if there are any bugs, questions, feature request, please let me know!

What’s Changed

Full Changelog: Comparing v4.2.3...v5.0.0 · TheFes/cheapest-energy-hours · GitHub

1 Like

@TheFes love your template. thanks a lot!

one question: I want to stop my heat pump in the most expensive 3 hours per day. How can I get that? If I use hours=21 it’s for sure not working as expected, because the output will always be 0:00. Any idea? :slight_smile:

EDIT: found it in the documentation. need to set lowest=false. thanks again! :slight_smile:

1 Like

is it possible to get true/false when using mode="end"? something like mode="end_is_now"? :smiley:

How would that work? is_now returns true when the current time is between the start and end of the cheapest (or most expensive) hours.

If you want to trigger on the end up that period you can:

  • Create a template sensor with device_class: timestamp and use the template with mode='end' and the use a time trigger using that template sensor
  • Create a template binary sensor using mode='is_now' and use a state trigger on that binary sensor with to='off' and from='on'
1 Like

@TheFes just tried it, but I have a problem.

with

{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
{% set output = cheapest_energy_hours(sensor='sensor.nordpool_kwh_de_lu_eur_3_10_0', hours=3, end="12:00", lowest=false, time_format='time24') %}
{{ output }}

{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
{% set output = cheapest_energy_hours(sensor='sensor.nordpool_kwh_de_lu_eur_3_10_0', hours=3, end="12:00", lowest=false, time_format='time24', mode="is_now") %}
{{ output }}

I’ll get

09:00

False

When looking on my watch it’s 09:15, so the second statement should report “True”. What am I missing? :slight_smile:

@nicx It is after 9:00, so the cheapest period was between 6:00 and 9:00.
As it is now 9:15 (actually 9:22), now isn’t between 6:00 and 9:00 so mode='is_now' returns false

@TheFes but I use lowest=false, so currently I have the most expensive hours from 9 to 12. so it should return “true”. am I wrong?

Oh sorry, I thought you were using mode='end' in the first one. I will check

1 Like

Hmm, is_now seems to be broken, need to dig some deeper to find out why that is.

Update

v5.0.1

:bug: BUG FIXES

  • Fix for mode='is_now' which wasn’t working properly

What’s Changed

Full Changelog: Comparing v5.0.0...v5.0.1 · TheFes/cheapest-energy-hours · GitHub

Should be fixed now

1 Like

your the best, its working now. thanks for your lightning fast support :slight_smile:

1 Like

hopefully my last question: I created some template helpers via the UI. There is a comment “This template is updated at the beginning of every minute.”. So I thought this is enough for get things working. Unfortunately the template is not updating.
Am I missing something?

What is your template?

@TheFes for example this:

{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
{% set output = cheapest_energy_hours(sensor='sensor.nordpool_kwh_de_lu_eur_3_10_0', hours=3, start="12:00", time_format='time24', lowest=false, mode="is_now") %}

If you change that to the template below, it will renew every minute

{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
{% set n = now() %}
{% set output = cheapest_energy_hours(sensor='sensor.nordpool_kwh_de_lu_eur_3_10_0', hours=3, start="12:00", time_format='time24', lowest=false, mode="is_now") %}
{{ output }}
1 Like

Hi,

Is there something I’m doing wrong or…?

I’m trying below, but get “TypeError: unsupported operand type(s) for -: ‘str’ and ‘str’”

{%- set sensor = "sensor.electricity_price_2days" -%}
{% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
{{ cheapest_energy_hours(sensor=sensor, hours=2, start="22:00", end="08:00", include_tomorrow=true) }}

I use this sensor data

Prices:
  - price: 0.1
    readingDate: "2023-12-12T23:00:00Z"
  - price: 0.08
    readingDate: "2023-12-13T00:00:00Z"
  - price: 0.08
    readingDate: "2023-12-13T01:00:00Z"
  - price: 0.08
    readingDate: "2023-12-13T02:00:00Z"
  - price: 0.08
    readingDate: "2023-12-13T03:00:00Z"
  - price: 0.1
    readingDate: "2023-12-13T04:00:00Z"
  - price: 0.1
    readingDate: "2023-12-13T05:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T06:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T07:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T08:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T09:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T10:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T11:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T12:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T13:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T14:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T15:00:00Z"
  - price: 0.13
    readingDate: "2023-12-13T16:00:00Z"
  - price: 0.13
    readingDate: "2023-12-13T17:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T18:00:00Z"
  - price: 0.12
    readingDate: "2023-12-13T19:00:00Z"
  - price: 0.11
    readingDate: "2023-12-13T20:00:00Z"
  - price: 0.11
    readingDate: "2023-12-13T21:00:00Z"
  - price: 0.1
    readingDate: "2023-12-13T22:00:00Z"
  - price: 0.1
    readingDate: "2023-12-13T23:00:00Z"
  - price: 0.1
    readingDate: "2023-12-14T00:00:00Z"
  - price: 0.1
    readingDate: "2023-12-14T01:00:00Z"
  - price: 0.1
    readingDate: "2023-12-14T02:00:00Z"
  - price: 0.1
    readingDate: "2023-12-14T03:00:00Z"
  - price: 0.1
    readingDate: "2023-12-14T04:00:00Z"
  - price: 0.11
    readingDate: "2023-12-14T05:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T06:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T07:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T08:00:00Z"
  - price: 0.13
    readingDate: "2023-12-14T09:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T10:00:00Z"
  - price: 0.12
    readingDate: "2023-12-14T11:00:00Z"
  - price: 0.12
    readingDate: "2023-12-14T12:00:00Z"
  - price: 0.12
    readingDate: "2023-12-14T13:00:00Z"
  - price: 0.13
    readingDate: "2023-12-14T14:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T15:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T16:00:00Z"
  - price: 0.15
    readingDate: "2023-12-14T17:00:00Z"
  - price: 0.13
    readingDate: "2023-12-14T18:00:00Z"
  - price: 0.12
    readingDate: "2023-12-14T19:00:00Z"
  - price: 0.12
    readingDate: "2023-12-14T20:00:00Z"
  - price: 0.11
    readingDate: "2023-12-14T21:00:00Z"
  - price: 0.11
    readingDate: "2023-12-14T22:00:00Z"
unit_of_measurement: EUR/kWh
friendly_name: Electricity price 2days

For me that exact template works, but it uses the Nordpool sensor.
Where does your price data come from

EnergyZero


sensor:
  # "Elektriciteit (uur) prijs vandaag en morgen" bevat de stroomkosten (inkoopprijs)
  - platform: rest
    name: Electricity price 2days
    unique_id: energyzero_electricity_hour_price_2days
    resource: https://api.energyzero.nl/v1/energyprices
    unit_of_measurement: "EUR/kWh"
    scan_interval: 3600
    value_template: '{{ value_json.Prices[now().hour].price|float(0) }}'
    json_attributes:
      - Prices
  # Set start_date to 00:00:00 today and end_date to 23:59:59 tomorrow and convert to UTC
    params:
      fromDate: >
        {%- set tz = int(now().strftime("%z")[-5:-2]) -%}
        {%- set date = as_datetime(now().strftime("%Y-%m-%d 00:00:00"))-timedelta(hours=tz) -%}
        {{ date.isoformat() }}Z
      tillDate: >
        {%- set tz = int(now().strftime("%z")[-5:-2]) -%}
        {%- set date = as_datetime(now().strftime("%Y-%m-%d 23:59:59"))-timedelta(hours=tz) -%}
        {{ (date + timedelta(days=1)).isoformat() }}Z
      interval: 4
      usageType: 1
      inclBtw: true