Home Assistant: Nord Pool Spot prices and how to automate devices for cheapest hours

Most of this information can already be found around the internet, but since I wanted to integrate one as well for my Home Assistant instance, why not to share the same with you?

So, the energy prices have gone up by a mile. The best way to balance the energy bill is to schedule some devices to cheapest hours of the day.. those devices could be something like washing machines, water boilers or car charging? Most of the devices needs a specific timeframe to run so sequential cheap hours are needed instead of scattered ones. It’s generally a bad idea to run a washing machine at 01:00, pause it at 02:00 and then continue at 05:00 🙂

Note: There’s now also an advanced version available now that uses local calendar and has support to stretch the sequence between two days (e.g. 22.00 – 06.00). More details can be found from a separate blog post.

Integrating Nord Pool to the Home Assistant

First we need to get the Nord Pool prices into the system so a new integration is needed. Luckily (again) someone has already made it happen and it can be installed using HACS. If you don’t know what HACS is, it’s a Home Assistant Community Store, a place where tons of integrations and frontend elements are published and can be easily installed. Just need to remember that those are not official Home Assistant components and are maintained by individual people and therefore can break more easily than official ones.

Anyway, start by installing Nord Pool custom component. This integration does not have UI configuration so the has to be done manually to the configurations.yaml.

For example, here’s my config:

nordpool:

sensor:
  - platform: nordpool
    VAT: true
    currency: "EUR"
    price_in_cents: true
    region: "FI"
    precision: 3
    price_type: kWh

In above configration firstly we enable the integration and second we configure the sensor to use region Finland and currency in Euros. Some tweaking to precision and VAT is also done, but more configuration details can be found from the integration repository.

After everything is configured, restart the Home Assistant and continue to the UI part.

Making the UI

First let’s find the entity id from our integrations. In Home Assistant go to Settings->Devices & Services->Entities and search with the filter ‘nordpool‘. You should find a entity id (that is same as unique id) and write it down somewhere. Unique id is generated by the Nord Pool integration from the configuration we made in the previous chapter.

For this part I’m using Apex Charts Card that is very versatile graphical presentation card of various values. Again, this frontend card can be installed using HACS. So go to the HACS and search for Apex and install it.

After installation is succeeded, it’s time to configure two cards (one for today and one for tomorrow).

Here’s my configuration for the cards (example UI shown at the end of this chapter):

type: 'custom:apexcharts-card'
graph_span: 24h
header:
  title: Energy price today (snt/kWh)
  show: true
span:
  start: day
now:
  show: true
  label: Now
series:
  - entity: sensor.nordpool_kwh_fi_eur_3_10_024
    type: column
    data_generator: |
      return entity.attributes.raw_today.map((start, index) => {
        return [new Date(start["start"]).getTime(), entity.attributes.raw_today[index]["value"]];
      });
type: custom:apexcharts-card
graph_span: 1d
header:
  title: Energy price tomorrow (snt/kWh)
  show: true
span:
  start: day
  offset: +1d
series:
  - entity: sensor.nordpool_kwh_fi_eur_3_10_024
    type: column
    data_generator: |
      return entity.attributes.raw_tomorrow.map((start, index) => {
        return [new Date(start["start"]).getTime(), entity.attributes.raw_tomorrow[index]["value"]];
      });

Again, you can use the same configurations, just remember to change the proper entity id for the cards (the one that we wrote down in previous chapter).

That’s it! Now there should be todays electrical spot prices and tomorrows spot prices available in the Home Assistant for any use!

To make it smart keep on reading the next chapters..

Finding the cheapest hours

One of the key points of having Nord pool is of course finding the cheapest hour(s) and automate devices to run during those hours.

For this I’ve created a template sensor that ensures validity of next day prices (usually published by Nord Pool at 12:00) and finds the sweet spot of requested lenght!

It’s easily modifiable by changing numberOfSequentialHours (how long period are we looking for), firstHour (first possible hour we want to start) and last hour (final hour we want to stop latest). E.g. you can also use it to find cheapest hours during the next night by setting the last hour to something like 06:00.

Note: There’s now also an advanced version available now that uses local calendar and has support to stretch the sequence between two days (e.g. 22.00 – 06.00). More details can be found from a separate blog post. You might want to skip into that blog post from this point forward..

sensor:
  - platform: template
    sensors:
      cheapest_hours_energy_tomorrow:
        device_class: timestamp
        friendly_name: Cheapest sequential electricity hours
        value_template: >
          {%- set numberOfSequentialHours = 3 -%}
          {%- set lastHour = 23 -%}
          {%- set firstHour = 0 -%}

          {%- if state_attr('sensor.nordpool_kwh_fi_eur_3_10_024', 'tomorrow_valid') == true -%}
            {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at("00:00") + timedelta( hours = (24)), cheapestPrice=999.00) -%}
            {%- for i in range(firstHour + numberOfSequentialHours, lastHour+1) -%}
              {%- set ns.counter = 0.0 -%}
              {%- for j in range(i-numberOfSequentialHours, i) -%}
                {%- set ns.counter = ns.counter + state_attr('sensor.nordpool_kwh_fi_eur_3_10_024', 'tomorrow')[j] -%}
              {%- endfor -%}
              {%- set ns.list = ns.list + [ns.counter] -%}
              {%- if ns.counter < ns.cheapestPrice -%}
                {%- set ns.cheapestPrice = ns.counter -%}
                {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (24 + i - numberOfSequentialHours)) -%}
              {%- endif -%}
            {%- endfor -%}
            {{ ns.cheapestHour }}
            {%- set ns.cheapestPrice = ns.cheapestPrice / numberOfSequentialHours -%}
          {%- endif -%}


Finally making the automations

Now that we know the cheapest hours for the next day, only thing to do is actually do the automations for various devices.

My automation is actually in two parts:

  • First I save the cheapest hour(s) at 23:10, because we can’t be following ‘tomorrow’ state after midnights since it obviously is not ‘tomorrow’ anymore after that 🙂
    • UPDATE: Time changed from 18:00 to 23:10. If cheapest hours happens between automation start time and midnight, the device might start on a wrong day!
  • Second I run the automation when the time trigger is hit
# Helper to keep the start time
input_datetime:
  device_start_time:
    name: Device Start Time
    has_time: true
    has_date: false

automation:
# Update time trigger to cheapest hours
  - id: '1663398489357'
    alias: 'Set device start time'
    description: ''
    trigger:
    - platform: time
      at: '23:10:00'
    condition:
    - condition: not
      conditions:
      - condition: state
        entity_id: sensor.cheapest_hours_energy_tomorrow
        state: unknown
    action:
    - service: input_datetime.set_datetime
      data:
        time: '{{ as_timestamp(states(''sensor.cheapest_hours_energy_tomorrow'')) | timestamp_custom(''%H:%M'') }}'
      target:
        entity_id: input_datetime.device_start_time
    mode: single

# Finally do the actions when time trigger is hit
  - id: '1663399614818'
    alias: Increase heating
    description: ''
    trigger:
    - platform: time
      at: input_datetime.device_start_time
    condition: []
    action:
    - service: climate.set_temperature
      data:
        temperature: 24
      target:
        entity_id: climate.heat_pump
    mode: single

Nord pool integration have had some issue of not updating properly from time to time (not happened to me though), so with this kind of automation previous day time schedule is being kept if the price template fails for a reason or another. That way no critical devices are left in cold (like water heater).

In my example I’m just increasing heating values, change it to anything you wish to use on cheap hours. Just remember to turn it off as well if the action is not just simple launch action.

If you made it this far, you can also check the ready made Home Assistant package in the next post!

Conclusion

So far so good.. even though I’m currently having still a static electric price for some months, I’m using this automation for the common good. I believe that if it’s possible to time electric consumption for the cheapest hours it will eventually balance the high and lows in general.

Anyway, possibilities to run devices at cheaper prices are great as long as the device has a remote control option! This type of behaviour could be also used for heating to reserve heat into structural parts of the house (e.g. floor heating) during nights.


Did you find this guide helpful? You can keep the blog going by bidding me a coffee!

170 Replies to “Home Assistant: Nord Pool Spot prices and how to automate devices for cheapest hours”

  1. Shouldn’t it be

    {%- if ns.counter < numberOfSequentialHours*ns.cheapestPrice -%}
    {%- set ns.cheapestPrice = ns.counter / numberOfSequentialHours -%}
    {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (24 + i – numberOfSequentialHours)) -%}

    ?

    1. Actually you are perfectly right, thanks for pointing the bug out!

      However, I’d rather fix it by removing the whole average calculation and add it on the end (even though the avg. price is not even used).

      I’ve been running this automation myself with a broken implementation (it’s been quite accurate still though) 😀

      Fixed the issue in the post now!

  2. I’m extremely grateful to Your post, during this extremely perfect time 😉
    I have spent couple of nights studying HASS & how to solve hitting heat pump on during cheapest hours. With Toni’s good instructions I have already managed to switch my Shelly on when cheapest hour has been reached, but I’m still lacking how to switch it off, when desired amount of “cheap” hours has been gone. Do you have any idea for this?

    Side note: During today, I have HASS coding experiense almost 48 hours =)

    1. Hi and thanks for the nice feedback!

      There’s couple of ways you can do to switch the things off:
      1. Make another input_datetime entity and set time to it in the same automation with the start time like this: (I use this method)
      device_end_time:
      name: Device End Time
      has_time: true
      has_date: false


      - id: '1663398489322'
      alias: 'Set device/end start time'
      description: ''
      trigger:
      - platform: time
      at: '08:37:00'
      condition:
      - condition: not
      conditions:
      - condition: state
      entity_id: sensor.cheapest_hours_energy_tomorrow
      state: unknown
      action:
      - service: input_datetime.set_datetime
      data:
      time: '{{ as_timestamp(states(''sensor.cheapest_hours_energy_tomorrow'')) | timestamp_custom(''%H:%M'') }}'
      target:
      entity_id: input_datetime.device_start_time
      - service: input_datetime.set_datetime
      data:
      time: '{{ ((as_timestamp(states(''sensor.cheapest_hours_energy_tomorrow'')) + (3600*3)) | timestamp_custom(''%H:%M'')) }}'
      target:
      entity_id: input_datetime.device_end_time
      mode: single

      ..and then of course make automation that listens for this input_datetmime.device_end_time entity and turns off the device.

      2. In the launch action you can use ‘delay’ to wait for 3 hours and then switch the device off. This way the automation stays ‘active’ until process is fully finished (turned on and off).
      For this I do not have an example right now, but I think you got the idea 🙂

      1. Great. Thanks for the fast repply.
        Yes I’ll survive with the device activation part. But that option no. 2… how obvious 🙂

  3. Nice work, It look a lot better than my current code. I have a question if I set firsthours to 22:00 and last hour to 6:00, will it work to search for a six hour span that start at 23 tonight? //Erik

    1. Nope, this currently works only on one full day.

      But your question is something that I’ve been also thinking of implementing.. for that we would need to combine nord pool ‘today’ and ‘tomorrow’ into one array and loop that through. Shouldn’t be too difficult to do some rainy day 🙂

        1. It has rained,
          I hope it will take in account both today and tomorrow when finding cheapest hour to start.
          cheapest_hours_energy_tomorrow:
          device_class: timestamp
          friendly_name: Cheapest sequential electricity hours
          value_template: >
          {%- set numberOfSequentialHours = 1 -%}
          {%- set lastHour = 23 + 24 -%}
          {%- set firstHour = 13 -%}

          {%- if state_attr(‘sensor.el_spotpris’, ‘tomorrow_valid’) == true -%}
          {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at(“00:00”) + timedelta( hours = (24)), cheapestPrice=999.00) -%}
          {%- for i in range(firstHour + numberOfSequentialHours, lastHour + 1) -%}
          {%- set ns.counter = 0.0 -%}
          {%- for j in range(i-numberOfSequentialHours, i) -%}
          {%- if j < 24 -%}
          {%- set ns.counter = ns.counter + state_attr('sensor.el_spotpris', 'today')[j] -%}
          {%- else -%}
          {%- set ns.counter = ns.counter + state_attr('sensor.el_spotpris', 'tomorrow')[j-24] -%}
          {%- endif -%}
          {%- endfor -%}
          {%- set ns.list = ns.list + [ns.counter] -%}
          {%- if ns.counter < ns.cheapestPrice -%}
          {%- set ns.cheapestPrice = ns.counter -%}
          {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (i – numberOfSequentialHours -2)) -%}
          {%- endif -%}
          {%- endfor -%}
          {{ ns.cheapestHour }}
          {%- set ns.cheapestPrice = ns.cheapestPrice / numberOfSequentialHours -%}
          {%- endif -%}

          1. A mistake
            {%- set ns.cheapestHour = today_at(“00:00”) + timedelta( hours = (i – numberOfSequentialHours -2)) -%}
            should be
            {%- set ns.cheapestHour = today_at(“00:00”) + timedelta( hours = (i – numberOfSequentialHours )) -%}

          2. Nope this doesn’t work either. I have a new version I’ll check the next 24h before posting a mistake again…. 🙂

          3. This code works.
            sensors:
            billigaste_energi_hushallsmaskiner:
            device_class: timestamp
            friendly_name: billigaste energi hushallsmaskiner

            value_template: >
            {%- set numberOfSequentialHours = states(‘input_number.num_timmar_till_hushallsmaskiner’) | int() -%}
            {%- set lastHour = 24 -%}
            {%- set firstHour = ((as_timestamp(now() ) | timestamp_custom(‘%H’)) | int() ) -%}
            {%- if state_attr(‘sensor.el_spotpris’, ‘tomorrow_valid’) == true and now().hour >=13-%}
            {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at(“00:00”) + timedelta( hours = (24)), cheapestPrice=999.00) -%}
            {%- for i in range(firstHour + numberOfSequentialHours, lastHour +9 ) -%}
            {%- set ns.counter = 0.0 -%}
            {%- for j in range(i-numberOfSequentialHours, i) -%}
            {%- if j < 24 -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.el_spotpris', 'today')[j] -%}
            {%- else -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.el_spotpris', 'tomorrow')[j-24] -%}
            {%- endif -%}
            {%- endfor -%}
            {%- set ns.list = ns.list + [ns.counter] -%}
            {%- if ns.counter < ns.cheapestPrice -%}
            {%- set ns.cheapestPrice = ns.counter -%}
            {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (i – numberOfSequentialHours )) -%}
            {%- endif -%}
            {%- endfor -%}
            {{ ns.cheapestHour }}
            {%- set ns.cheapestPrice = (ns.cheapestPrice / numberOfSequentialHours) -%}
            {%- else -%}
            {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at("00:00") + timedelta( hours = (24)), cheapestPrice=999.00) -%}
            {%- for i in range(firstHour + numberOfSequentialHours, lastHour + 1) -%}
            {%- set ns.counter = 0.0 -%}
            {%- for j in range(i-numberOfSequentialHours, i) -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.el_spotpris', 'today')[j] -%}
            {%- endfor -%}
            {%- set ns.list = ns.list + [ns.counter] -%}
            {%- if ns.counter < ns.cheapestPrice -%}
            {%- set ns.cheapestPrice = ns.counter -%}
            {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (i – numberOfSequentialHours )) -%}
            {%- endif -%}
            {%- endfor -%}
            {{ ns.cheapestHour }}
            {%- set ns.cheapestPrice = (ns.cheapestPrice / numberOfSequentialHours) -%}
            {%- endif -%}

          4. Tanks a lot Erik!

            I think this your piece of code will help others that are trying to setup two day automation as well 🙂

          5. Hi Erik.
            I don’t seem to get your two day sensor working.
            I think it because intends(configuration valitadion wont pass), but i can’t figure it out.
            Any help, please?

          6. Great work Erik and Toni! Got my automations working perfectly with your examples. I have found that attribute “tomorrow_valid” is’nt reliable anymore. Instead I’m using:
            {% if state_attr(“sensor.YOURNORDPOOLSENSORNAME”, “tomorrow”) [1] is number == true %}
            Also for those who use code from comments of this site. I had to replace minus hyphens from code after pasting to my text editor. Propably due to textcoding differenses with this site and my editor.
            So safe bet is to rewrite whole code your self and use code from this site as refrence. Below is my code (mostly copied from Erik) that works for my automations for now:
            #Note I have added helper “input_number.sequentialhours” and sensor “sensor.time” for automation reasons, but you can use your own variables here or just integers.

            cheapest_sequential_spot_hours_input:
            device_class: timestamp
            friendly_name: Cheapest sequential hours
            value_template: >
            {%- set numberOfSequentialHours = states(‘input_number.sequentialhours’) | int() -%}
            {%- set lastHour = 24 -%}
            {%- set firstHour = states(“sensor.time”)[0:2]|int -%}
            {%- if state_attr(‘sensor.nordpool_kwh_fi_eur_1_10_024’, ‘tomorrow_valid’) == true and state_attr(“sensor.nordpool_kwh_fi_eur_1_10_024”, “tomorrow”) [1] is number == true -%}
            {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at(“00:00”) + timedelta( hours = (24)), cheapestPrice=999.00) -%}
            {%- for i in range(firstHour + numberOfSequentialHours, lastHour +25 ) -%}
            {%- set ns.counter = 0.0 -%}
            {%- for j in range(i-numberOfSequentialHours, i) -%}
            {%- if j < 24 -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.nordpool_kwh_fi_eur_1_10_024', 'today')[j] -%}
            {%- else -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.nordpool_kwh_fi_eur_1_10_024', 'tomorrow')[j-24] -%}
            {%- endif -%}
            {%- endfor -%}
            {%- set ns.list = ns.list + [ns.counter] -%}
            {%- if ns.counter < ns.cheapestPrice -%}
            {%- set ns.cheapestPrice = ns.counter -%}
            {%- set ns.cheapestHour=today_at("00:00") + timedelta( hours = (i – numberOfSequentialHours )) -%}
            {%- endif -%}
            {%- endfor -%}
            {{ ns.cheapestHour }}
            {%- set ns.cheapestPrice = (ns.cheapestPrice / numberOfSequentialHours) -%}
            {%- else -%}
            {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at("00:00") + timedelta( hours = (24)), cheapestPrice=999.00) -%}
            {%- for i in range(firstHour + numberOfSequentialHours, lastHour + 1) -%}
            {%- set ns.counter = 0.0 -%}
            {%- for j in range(i-numberOfSequentialHours, i) -%}
            {%- set ns.counter = ns.counter + state_attr('sensor.nordpool_kwh_fi_eur_1_10_024', 'today')[j] -%}
            {%- endfor -%}
            {%- set ns.list = ns.list + [ns.counter] -%}
            {%- if ns.counter < ns.cheapestPrice -%}
            {%- set ns.cheapestPrice = ns.counter -%}
            {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (i – numberOfSequentialHours )) -%}
            {%- endif -%}
            {%- endfor -%}
            {{ ns.cheapestHour }}
            {%- set ns.cheapestPrice = (ns.cheapestPrice / numberOfSequentialHours) -%}
            {%- endif -%}

          7. Hello Erik and Toni,
            What a wonderful work you guys have done! thank you so much. I am having a bit of an issue. I also would like to have a helper and change the number of sequential hours but when I use and helper and add it as an integer
            {%- set numberOfSequentialHours = states(‘input_number.sequentialhours’) | int() -%}

            I tried to make the helper “input_number.sequentialhours” from both UI and inside the yaml with the sensor but I got the same error:
            Error while processing template: Template(“{%- set numberOfSequentialHours = states(‘input_number.sequential_hours’)|int()-%} {%- set lastHour = 23 -%} {%- set firstHour = 0 -%} {%- if state_attr(‘sensor.nordpool_kwh_fi_eur_3_095_024’, ‘tomorrow_valid’) == true -%} {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at(“00:00″) + timedelta( hours = (24)), cheapestPrice=999.00) -%} {%- for i in range(firstHour + numberOfSequentialHours, lastHour+1) -%} {%- set ns.counter = 0.0 -%} {%- for j in range(i-numberOfSequentialHours, i) -%} {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_fi_eur_3_095_024’, ‘tomorrow’)[j] -%} {%- endfor -%} {%- set ns.list = ns.list + [ns.counter] -%} {%- if ns.counter < ns.cheapestPrice -%} {%- set ns.cheapestPrice = ns.counter -%} {%- set ns.cheapestHour = today_at("00:00") + timedelta( hours = (24 + i – numberOfSequentialHours)) -%} {%- endif -%} {%- endfor -%} {{ ns.cheapestHour }} {%- set ns.cheapestPrice = ns.cheapestPrice / numberOfSequentialHours -%} {%- endif -%}")
            Traceback (most recent call last):
            File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 420, in async_render
            render_result = _render_with_context(self.template, compiled, **kwargs)
            File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1933, in _render_with_context
            return template.render(**kwargs)
            File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 1301, in render
            self.environment.handle_exception()
            File "/usr/local/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
            raise rewrite_traceback_stack(source=source)
            File "”, line 15, in top-level template code
            ZeroDivisionError: float division by zero

            Any idea what could be wrong?

            Cheers,
            Kostas

    2. Erik/Arttu, I have tried your codes but I am not sure how (if) this works as I want.

      I want to limit the heating of my water to between 22:00 (today) and 05:00 (tomorrow).
      Can this be done and what start- and end time do I set?

      Thanks!

  4. Does the nord pool integration still work for you after the HA core 2022.9 update?
    Mine doesn’t 🙁

    1. Yes, I haven’t had any problems with it even after 2022.9.

      Have you checked the logs why it fails?

  5. Sorry, I am new to HA. I tried to put that template sensor code to configure.yaml but could not get it working. Could you please give me newbie guidance how to get it working?

    I got those Apex charts card working but not that template for cheapest hours.

    I

    1. I’ve got similar feedback from other channels as well so maybe this is not very clear to people that are new to HA.

      Anyway, template_sensor goes in the configurations.yaml, but the catch is if there’s already sensor: block existing.
      In that case, remove the “sensor:” from above and add after existing block.

      Same goes to automations.yaml, remove “automation:” in there before adding.

      input_datetime also goes into configurations.yaml. No need to find existing items for that one.

      Hopefully this helps. If not, please reply and let’s look more deep into it 🙂

      1. Thank you very much. Now it works and actually i feel really stupid not to got it by myself.

        Luckily there are people like you who makes this world much better place to live 🙂

    1. Hi!

      You can add delay to the start automation and wait for your ‘amount of hours’ after it.
      Other way would be to add another input_datetime that is being setup at the same when start time is updated.
      Then just trigger automation off on that time.

      Ps. I’ve received quite many questions related to this automation so I will write a follow-up near future with examples 🙂

      1. I’ve a delay to turn heater in the office off after 1h. What I need is a switch to turn automation off for example if I’m sick and not working tomorrow or so. Normally it’s always on on weekdays but sometimes I’m not using the office. I already have everything else working but I don’t know how to solve this and I’m sure I’m not the only one who needs this

        1. In that case dont use this automation to do any action other then Setting the start and End Time, then you use this start and End times in seperate automation ie ” when start time is x do this” and add a condition for when to run it like if the light is on in the office or if your mobile is present or create a virtual switch (toggle helper)

  6. Thank you so much for sharing this great project!
    Question: is it possible to somehow modify it to work on TODAY’s prices instead of tomorrow’s?

    1. Hi and thanks for the feedback!

      It should be possible quite easily.
      Just use ‘today’ values instead of ‘tomorrow’ and trigger the update same day at 00:00.

      Also tomorrow_valid check should be removed from the automation.

      1. Thanks for the reply!
        I tried doing the suggested modifications, but I don’t think I did it right, since the output seems incorrect to me (tomorrow works fine).
        Could I possibly bother you to paste a modified sensor for today here in a reply or to my gmail? I only have the bare minimum Yaml skills.
        Very grateful for any assistance!

        1. Hi!

          I don’t have example at the moment, but I’ll try it this weekend and post it here after that 🙂

          1. Hi Toni, great post! Has really inspired me to set automatic charging times for our EV. Just as Christoffer has asked, I’ve also tried to make a sensor showing ‘Cheapest electricity today’, but it just shows as unavailable. Would you mind trying to make a new version of the code, which looks for the cheapest time today? Would be greatly appreciated.

          2. Thanks for the feedback!

            This is on my worklist and will create a follow-up post containing this issue as well, but unfortunately I haven’t had time to write it fully yet.

  7. Hello,

    Thanks for sharing the project. This has given me the encouragement to try something similar. I’m planning to control relays with GPIO’s. I have managed to load and show the Nordpool data with Apex charts. In addition controlling relays works now manually by switches in UI. I’m a newbie with this HA. Should these entities “Device Start Time” & Device End Time” be updated on the time given in the code 23:10 & 8:37 when the prices are checked and cheapest hours determined? I’m suspecting that this “Finding the cheapest hours” does not currently work for me…

    1. Yes, the device start time (and end time, if you are using it) should be updated properly at 23:10.

      Do you get any value for the start time or does it stay ‘unknown’ ?

      1. I got the value 10:00 for the Start Time. But not functioning since it is not changing when I test. This entity ” Cheapest sequential electricity hours” does not show up to me. I wonder if I’m doing something wrong in this creation of template sensor. Is it so that all of the following code can be added to the configuration.yaml file, except the apex cards which I succesfully was able to add with the UI?

        1. The automation part goes into automations.yaml, except that you should remove the “automation:” line from it and set the idents properly as in the other automations you have in the file.

          1. Moikka Toni.

            Mulla on jäätäviä ongelmia automaation kanssa. Olen ottanut kuvat word-dokumenttiin mun config- ja automations -fileistä, joka löytyy tämän linkin takaa. Voisitko lyhyesti tsekata missä mättää?

            Olen pyrkinyt tekemään kaiken kuten blogissasi olet ohjeistanut, mutta device.start.time tai device.end.time (helpers) ei päivity ja siten ei kai valokaan (testi).

            https://1drv.ms/w/s!AhLSAx_PmMjU01_9CQ5huTOzfQfq?e=468WDy

          2. Hi!

            At least from automations.yaml lines 9 to 12 idents (sisennys) are wrong, add one more ident tab to those.
            If you need more assistant in Finnish, please send email to creatingsmarthome (at) gmail.com.

            I’ll try to keep this blog English only, since most of the readers are outside Finland 🙂

  8. Hi, and thanks a lot for your examples. I just starting up my automated heating with nordpool cheapest times. There is some things which i still need to understand and get some switch/sensors. But in mean while here are some additions to those charts. This adds values of current bar when hovering over it
    extremas: true
    float_precision: 3

    Also it shows peak points. There is also 24H setting to clock in xaxis (hours_12: false)

    type: custom:apexcharts-card
    graph_span: 24h
    hours_12: false
    header:
    title: Energy price today (cnt/kWh)
    show: true
    span:
    start: day
    now:
    show: true
    label: Now
    series:
    – entity: sensor.nordpool_kwh_fi_eur_3_05_024
    type: column
    show:
    extremas: true
    float_precision: 3
    data_generator: |
    return entity.attributes.raw_today.map((start, index) => {
    return [new Date(start[“start”]).getTime(), entity.attributes.raw_today[index][“value”]];
    });

  9. Thank you. It’s working as intended. As someone else asked about. How about a stop timer? I’m aware of the delay function, but it’s not recommended. Since your math and scripting are beyond mine, shouldn’t it be possible to create a set device stop? I tried with some minus etc. but no cigar.

    1. Hi and thanks for the feedback!

      If you see comment written 23/09/2022 at 05:41, I’ve already wrote a small example how to calculate the device stop time 🙂

      I’m planning to write a follow-up to display all these new information in another post once I manage to schedule some time for that.

      Will the comment (23/09/2022 at 05:41) help you or did you mean something else?

  10. Hi!

    Nice work… planning to do this kind of automation at my summer cottage…

    You have code at the beginning of page… is it updated/modified based these comments? So is it putting AC also off after cheapest hours?

    Also.. I don’t have knowledge to modify this… Is it “wise” to modify and ask you to modify this with low price / high price triggers?

    What I mean that there can be a trigger (best way is that you can modify trigger price with lovelace) where (if high price trigger) AC won’t go on at cheapest hours if price is higher than “high price trigger” and vice versa with low price trigger.

    1. The post itself does not turn anything, only turn on is written down on it. In the comments parts there are also some small pieces of code to turn the device off.

      It would be rather simple modification to just add some ‘high price/low price’ boolean value to check if the device start automation should be run or not.
      I would but the price condition inside ‘device start’ automation to ensure the price is within specified range and not touch the ‘cheapest hours’ template at all.

      These are quite far fetched cases related to this post, but I can help you to get started. You can mail me to creatingsmarthome (at) gmail.com if you need more detailed support with the automations 🙂

  11. Wow, what a fantastic article! I was able to get the Nordpool prices working really easily.

    This will save me a lot of money in the spring when our current contract expires and we move into spot pricing.

    Thank you so much! Excellent work.

  12. How can i swap this for most expensive sequential hours?

    I want to create 2 sensors with interval for example 00:00 – 12:00 and 13:00 – 23:00 and turn of heating during the 3 most expensive hours in each interval.

    1. The most convenient way would be to just change the the less than operator to greater than operator.. and of course I’d recommend to rename the “cheapestHours” variable to “highestHours” or something..

      Conversion like this:
      {%- if ns.counter < ns.cheapestPrice -%}
      =>
      {%- if ns.counter > ns.cheapestPrice -%}

      Disclaimer: I've not tested it out, but it 'should' work

      Edit:
      One more thing, the initial value should also be changed:
      cheapestPrice=999.00
      =>
      cheapestPrice=0.00

  13. Voitko tarkentaa noita ohjeita hieman aloittelijalle sopivammaksi, eli

    Mihin nuo kaksi Apex Charts Card korttia lisätään?
    Mihin tuo sensor-scripti lisätään ja mitä valintoja siihen liittyen mahdollisesti täytyy tehdä?
    Mihin tuo automaatioscripti lisätään ja mitä valintoja siihen liittyen mahdollisesti täytyy tehdä?

    Kiitos!

    1. Apex char cards can be added directly from the Home Assistant UI: Edit dashboard -> Add card -> Apex

      For the sensor and automations scripts, see comment written 05/10/2022 at 05:28. There are details in that one 🙂
      You just need to change the entity id and number of hours you are looking for.

      Ps. I’m replying in English since most of the blog readers are outside Finland 🙂

  14. Thanks for sharing. I copied the card code to get the prices show up as bars, and it works great:). Do you know if it would be possible to show used power history on top of the prices? That would mean to show a line graph with a power sensor as source on the same card, on top of the bars.

    1. Hi!

      I think it should be possible with Apex, but haven’t tried that. Nice idea anyway 🙂

  15. Hi,

    I’m using the nordpool integration. Most of the time it works just fine. However, sometimes a part of data is missing. In the example below, only the data for the first our of tomorrow is available:

    —– quotation, start——
    Tomorrow
    12.272, , , , , , , , , , , , , , , , , , , , , , ,

    Raw tomorrow
    – start: ‘2022-11-02T00:00:00+02:00’
    end: ‘2022-11-02T01:00:00+02:00’
    value: 12.272
    – start: ‘2022-11-02T01:00:00+02:00’
    end: ‘2022-11-02T02:00:00+02:00’ value: null
    – start: ‘2022-11-02T02:00:00+02:00’
    end: ‘2022-11-02T03:00:00+02:00’
    value: null
    —– quotation, end——

    Even rebooting rasp doesn’t load the data.
    Does someone else experience the same issue?
    Do you know any workaround?

    1. I’ve noticed some issues with the nordpool integration as well so that the dat wont load properly, but reloading the integration has been done the trick always.
      Try to use ‘reload integration’ from the integrations for nordpool and see if it helps (I think rebooting should do the same though).

      If it helps, you can set up an automation that reloads the integration for example every day at 14:00

  16. Hi,
    First, thanks for the great work… I think it would be useful addition to have the hours ordered from cheapest to most expensive. Then one could for example choose 4 cheapest hours for the water heater. Or any other use case which does not require sequential hours… I don’t “speak” yaml/jinja2 very well, so I can’t do it… 🙁

    1. That’s a great addition as well. I haven’t myself (yet) seen a purpose for that in my environment, but I’m certain the time will come to utilise that kind of behaviour too.
      Let see if I can find a time slot from somewhere to implement that kind of behaviour.

      1. Hello Toni
        Thanks for your effort adding this feature. I still have a fixed price 0.73 sek until next year. But because of the situation I have started to prepare my home(house) to handle higher prices. I have bought Tado smart thermostat for handle the heating of my house and Telldus to switch on and of the light depending on where I am and the actual prices. I have also ordered a Ngenicic Tune that will help me to simulate the outside temperature and force my Nibe heating system to work more economic.

        I have now a Polestar 2 and a Easee charger that I can control from HA. I have tested your code and it works, but I also waiting for a rainy day 🙂 when you perhaps will have time for adding 48 hours support for your smart code :).
        Charging the car when it cheaper will really save money, of course I have a timer both in easee and in the car , but it will be much better if it will be automatic. I wish you Good day
        Regards Jonas

        1. Hi!

          Thanks for the feedback!
          Two day automation is on my TODO list and will get to that right after I’ve finalised next post about installing cheapest hours automation through packages 🙂

          1. Hi again
            Starting to understand jinja2 now and also your code.
            I have a ongoing loop in my head thinking about this. 🙂

            A Good solution would be to do the calculation 15 pm everyday an then add 15 to 24 from today and 00 to 15 from tomorrow nordpool data. Then we will always have 24 hour data to eork with.

            A question to you Toni. What will happend with the data if the HA is rebooted or s powerloss after the calculstion is done? Will the data in the helper remain?
            Have a Nice day

          2. Hi!

            Yes, a good solution would be to have more configurable timeframe (24h) between today and the next day. This is also in my expanding worklist 🙂
            …but the data will need to be max. 24h otherwise a date would needed to be kept inside the helpers and that brings up quite much complexity again.

            For your questions: helpers will retain their values through restart. (This was not the case a year or couple ago, but today it is :-))

  17. Hi
    I’m a total newbie on this.
    But anyone have a clue why even the first step failed.
    I installed Norpool
    I found the ID
    I Installed the apex
    but then when i tried to make the Grapf UI Card with the example code

    It gives error
    Custom element doesn’t exist: apexcharts-card.

    type: custom:apexcharts-card
    graph_span: 24h
    header:
    …..

    1. Looks like apex-charts is not properly loaded as a resources in your UI.

      Have you tried to restart Home Assistant and/or clear cache of the browser?

  18. Hi,
    I’m quite new to HA and this is i a really nice thing to have because of the rising electrical prices. Have got the two cards working correctly but i haven’t figured out where should i copy the template and trigger automation? Should i paste something to configuration.yaml to?

      1. Hi,
        Minutes after my comment i saw you had did a new post 🙂 Super!
        I already tested that but got the message :

        bad indentation of a mapping entry (17:11)

        14 | {%- set lastHour = 23 -%}
        15 | {%- set firstHour = 0 -%}
        16 | # CHANGE-ME: change entity to your own se …
        17 | {%- if state_attr(‘sensor.nordp …
        —————-^
        18 | {%- set ns = namespace(counte …

        1. Sorry that’s my bad, I forgot to add the indentations to the added #CHANGE-ME comment blocks.
          Thanks for pointing that out!

          The fixed version should be in GitHub now 🙂

  19. Is there possibility for some kind of failsafe ? Eg. if for some unknown reason you won’t get nordpool data at all for long time (maybe week or something) it would still turn water heater on 02.00 am -> 06.00 am.

    1. Yep, this automation has failsafe to run the device based on previous day.

      Could be easily converted to run on static time e.g. 02-06 by creating an ‘else’ branch/condition in the ‘Set device start time’ automation.

  20. This is absolutely great Toni !

    I set up this yesterday for my EV charger. I replaced power key with Sonoff Mini and installed your solution to turn charger on. Voila ! Now I can charge my vehicle during cheapest hours in the night. It just works 🙂
    Keep up the good work 🙂

  21. This is very good. Just a few notes.

    To get 24 hour times you can add “hours_12: false” in the card. No more am/pm sillyness.

    Also this is a slightly simpler way to get the series data:
    return entity.attributes.raw_today.map((it, index) => {
    return [it[“start”], it[“value”]];
    });

    Looking forward to the next post 🙂

  22. I think some update broke this awesome template. I’ve been using it to start my dishwasher and was planning to expand the usage in the future. Now im getting invalid timestamp error. For me it seems fine…
    sensor.cheapest_hours_energy_tomorrow rendered invalid timestamp: #2022-11-24 00:00:00+02:00
    sensor.cheapest_hours_energy_tomorrow rendered invalid timestamp:
    sensor.cheapest_hours_energy_tomorrow rendered invalid timestamp: ###2022-11-25 02:00:00+02:00

    1. That’s intentional warning when the tomorrow hours can’t be calculated (e.g. failure or not yet loaded nordpool tomorrow prices).
      Ensure that the Nord Pool integration has properly fetched the values for tomorrow.

  23. Is there possibility to add more options to this ?
    Now I am using this to my water heater for cheapest 4 hours.
    But I am going to integrate my electric floor heaters this too.
    Problem is, that 4 hours is not going to be enough.
    I would need eg. cheapest 8 or maybe 10 hours.
    I think that I don’t even need the sequential cheap hours but scattered ones are probably even cheaper for the heaters.

  24. I think I found solution. I added this template sensor to configuration.yaml. Now it is easy to create automations based on Ranks. Eg,, Turn on this when Rank is below X and turn of this when Rank is above Y.

    template:
    – sensor:
    – name: “NordpoolRank”
    state: >
    {{ (state_attr(‘sensor.nordpool_kwh_fi_eur_3_10_024’, ‘today’) | sort).index(state_attr(‘sensor.nordpool_kwh_fi_eur_3_10_024’, ‘current_price’))+1}}

  25. Nordpool seems to be working slowly now and then and now they have issued statement, that technically automated data scraping from their system is not allowed, have you considered changing the cheapest hours script to work with Entso-e trasperancy platform?

    There is Enso-e integration for HA already, but format what it provides is a little different and unfortunately I dont have skills to modify your script to work with it

    https://github.com/JaccoR/hass-entso-e

    1. Yes, I’ve noticed also that the nordpool integration has been failing more recently, most probably because of increased usage of their API.

      I will have a look at the entso-e integration at some point, most probably it would suit more for this since it’s “fully” open.

    1. Det fungerer meget fint.
      Det ser ud til at være ret komplekst men virker desværre ikke for Nord Pool… Jeg skal bruge det til DK1 og DKK hos Nord Pool.

    2. It is working very fine.
      It seems to be rather complex but do unfortunately not work for Nord Pool… I need it for DK1 and DKK at Nord Pool.

  26. Any idea why is card with tomorrow’s prices not working? Just an wmpty chart with tomorrow’s date. Card with today’s prices works flawlessly. I have nordpool integration and apexcharts-card.

    1. Most probably same ‘problem’ as in previous comment: tomorrow price is published by Nordpool at about 16:00 CET. So before that time the prices are unknown.

  27. Hi. Thank you for posting this Toni. The sensor code you posted was not working the way I want it, but it got me thinking and also write a new example with some help from you 🙂

    I will use this to charge my car, and is based on todays prices and tomorrow (if available):
    – Start time is set to time.now.
    -stop is a helper input that sets the stop time when you want the charger to be finished.
    -seq is a helper input that is set to how long it’s going to charge.
    -based on start stop and seq, the code will calculate the cheapest sequential hours from start to stop.

    -also added some lines to avoid error if you set seq bigger than stop – start etc. – see code
    – if the code is calculating that start time should be the hour you are in, it will add some minutes dalay before the actual start time. This is because I will use a button to send start and stop time to charger/vehicle.

    The code is messy and no comments, but maybe it can help some of you, either the whole code, or just some of it 🙂

    {% set start = now().hour %}
    {% set stop = states(‘input_number.stopp’) | int() %}
    {% set seq = states(‘input_number.sequential’) | int() %}

    {% set x = state_attr(‘sensor.nordpool_kwh_krsand_nok_3_10_025’, ‘today’) %}
    {% set y = state_attr(‘sensor.nordpool_kwh_krsand_nok_3_10_025’, ‘tomorrow’) %}
    {% set data = namespace(numbers=[],value=[],count=0,max=99999, max_pos=0,cheapestHour=today_at(“00:00”) + timedelta( hours = (24))) %}

    {% if state_attr(“sensor.nordpool_kwh_krsand_nok_3_10_025”, “tomorrow”) [1] is number == true and stop (24 – now().hour) + stop %}
    {% set seq = (24 – now().hour) + stop %}
    {% endif %}

    {% set loops = stop+(25-start)-(seq)%}
    {% set rest_day = 25 – now().hour %}
    {% for i in range(24) %}
    {% if i >= start %}
    {% set data.numbers = data.numbers + [i] %}
    {% set data.value = data.value + [x[i]] %}
    {% endif%}
    {% endfor %}
    {% for i in range(rest_day + 23) %}
    {% if ((i <=start and i < stop) ) %}
    {% set data.numbers = data.numbers + [i] %}
    {% set data.value = data.value + [y[i]] %}
    {% endif %}
    {% endfor %}

    {% set counter = 0 %}
    {% for i in range(loops) %}
    {% set data.count = 0.0%}
    {% for j in range(seq) %}
    {% set data.count = data.count + data.value[i+j] %}
    {% endfor %}
    {% if data.count < data.max %}
    {% set data.max = data.count %}
    {% set data.max_pos = i %}

    {% set counter = counter + i%}
    {% if counter == 0 and now().minute < 55 %}
    {% if stop <= now().hour and (data.numbers[i] < now().hour) %}
    {% set data.cheapestHour = today_at("23:00") + timedelta( hours = (data.numbers[data.max_pos]+1), minutes= (now().minute) + 2)%}
    {% else %}
    {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ), minutes= (now().minute) + 2)%}
    {% endif %}

    {% else %}
    {% if stop <= now().hour and (data.numbers[i] < now().hour) %}
    {% set data.cheapestHour = today_at("23:00") + timedelta( hours = (data.numbers[data.max_pos]+1))%}
    {% else %}
    {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ))%}
    {% endif %}
    {% endif %}

    {% endif %}
    {% endfor %}
    {{ data.cheapestHour }}

    {% else %}
    {% if stop = (stop – start)%}
    {% if (stop – start) == 1 %}
    {% set data.cheapestHour = today_at(“00:00”) + timedelta( hours = (now().hour + 1))%}
    {% endif %}
    {% set seq = 1%}
    {% endif %}
    {% set loops = stop-start-seq+1%}

    {% for i in range(24) %}
    {% if (i >= start and i < stop) %}
    {% set data.numbers = data.numbers + [i] %}
    {% set data.value = data.value + [x[i]] %}
    {% endif %}
    {% endfor %}
    {% set counter = 0 %}
    {% for i in range(loops) %}
    {% set data.count = 0.0%}
    {% for j in range(seq) %}
    {% set data.count = data.count + data.value[i+j] %}
    {% endfor %}
    {% if data.count < data.max %}
    {% set counter = counter + i%}
    {% set data.max = data.count %}
    {% set data.max_pos = i %}
    {% if counter == 0 and now().minute < 55 %}
    {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ), minutes= (now().minute) + 3)%}
    {% else %}
    {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ))%}
    {% endif %}
    {% endif %}
    {% endfor %}
    {{ data.cheapestHour}}
    {% endif %}

      1. Hi. I don’t know why all the “” is ending up wrong. Tried yor way now. This code is working for me. If there is something wrong, it’s the “” ” and – 🙂

        {% set start = states(‘input_number.start’) | int() %}
        {% set stop = states(‘input_number.stopp’) | int() %}
        {% set seq = states(‘input_number.sequential’) | int() %}

        {% set x = state_attr(‘sensor.nordpool_kwh_krsand_nok_3_10_025’, ‘today’) %}
        {% set y = state_attr(‘sensor.nordpool_kwh_krsand_nok_3_10_025’, ‘tomorrow’) %}
        {% set data = namespace(numbers=[],value=[],count=0,max=99999, max_pos=0,cheapestHour=today_at(“00:00”) + timedelta( hours = (24))) %}

        {% if state_attr(“sensor.nordpool_kwh_krsand_nok_3_10_025”, “tomorrow”) [1] is number == true and stop (24 – now().hour) + stop %}
        {% set seq = (24 – now().hour) + stop %}
        {% endif %}

        {% set loops = stop+(25-start)-(seq)%}
        {% set rest_day = 25 – now().hour %}
        {% for i in range(24) %}
        {% if i >= start %}
        {% set data.numbers = data.numbers + [i] %}
        {% set data.value = data.value + [x[i]] %}
        {% endif%}
        {% endfor %}
        {% for i in range(rest_day + 23) %}
        {% if ((i <=start and i < stop) ) %}
        {% set data.numbers = data.numbers + [i] %}
        {% set data.value = data.value + [y[i]] %}
        {% endif %}
        {% endfor %}

        {% set counter = 0 %}
        {% for i in range(loops) %}
        {% set data.count = 0.0%}
        {% for j in range(seq) %}
        {% set data.count = data.count + data.value[i+j] %}
        {% endfor %}
        {% if data.count < data.max %}
        {% set data.max = data.count %}
        {% set data.max_pos = i %}

        {% set counter = counter + i%}
        {% if counter == 0 and now().minute < 55 %}
        {% if stop <= now().hour and (data.numbers[i] < now().hour) %}
        {% set data.cheapestHour = today_at("23:00") + timedelta( hours = (data.numbers[data.max_pos]+1), minutes= (now().minute) + 2)%}
        {% else %}
        {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ), minutes= (now().minute) + 2)%}
        {% endif %}

        {% else %}
        {% if stop <= now().hour and (data.numbers[i] < now().hour) %}
        {% set data.cheapestHour = today_at("23:00") + timedelta( hours = (data.numbers[data.max_pos]+1))%}
        {% else %}
        {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ))%}
        {% endif %}
        {% endif %}

        {% endif %}
        {% endfor %}
        {{ data.cheapestHour }}

        {% else %}
        {% if stop == 0 %}
        {% set stop = 24 %}
        {% endif %}
        {% if stop = (stop – start)%}
        {% if (stop – start) == 1 %}
        {% set data.cheapestHour = today_at(“00:00”) + timedelta( hours = (now().hour + 1))%}
        {% endif %}
        {% set seq = 1%}
        {% endif %}
        {% set loops = stop-start-seq+1%}

        {% for i in range(24) %}
        {% if (i >= start and i < stop) %}
        {% set data.numbers = data.numbers + [i] %}
        {% set data.value = data.value + [x[i]] %}
        {% endif %}
        {% endfor %}
        {% set counter = 0 %}
        {% for i in range(loops) %}
        {% set data.count = 0.0%}
        {% for j in range(seq) %}
        {% set data.count = data.count + data.value[i+j] %}
        {% endfor %}
        {% if data.count < data.max %}
        {% set counter = counter + i%}
        {% set data.max = data.count %}
        {% set data.max_pos = i %}
        {% if counter == 0 and now().minute < 55 %}
        {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ), minutes= (now().minute) + 3)%}
        {% else %}
        {% set data.cheapestHour = today_at("00:00") + timedelta( hours = (data.numbers[data.max_pos] ))%}
        {% endif %}
        {% endif %}
        {% endfor %}
        {{ data.cheapestHour}}
        {% endif %}

  28. Awesome project, thank for that.
    Is it possible to create two time frames within a specified timeframe. One time frame with the lowest prices and one time frame with the second best timeframe.

    I do have w2 EV’s, but I can only charge one car at the time. So it would be nice to have a time frame for lets say 2 cheapest hours of my first car and the second best 2 cheapest hours for my second car.

    1. Very interesting idea you have there!

      And of course it’s possible, but not directly with this automation and (for now) I don’t have ready made code for you.
      But if you are planning to implement something like this I can figure out two things that needs to be taken care of:
      1. Cheapest slots can not overlap each other so when calculating second slot hours we need to skip the first slot hours totally
      2. If first slot hours are e.g. between 01:00-03:00, then the first hour of second slot should not be count to the since it’s not long enough..

  29. Thanks! Was looking for this.. sadly my Nordpoon sensor says “tomorrow_valid: true” now still in the morning.. but that must be wrong? I mean the tomorrow-prices isnt released until after lunch right?

    But anyway, the time-stamp your code generate is it that hour +2 hours that is the 3 cheapest then?

    1. That sounds like a bug in the nordpool integration.. of course tomorrow_valid shouldn’t be true before CET 16:00.

      And for the second question, I didn’t quite get it. Can you please rephrase it? 🙂

      1. Thank you! What I was asking (sorry) was that if I set 3 hours as numberOfSequentialHours and the time is 22. Do that mean that at 22 and +2 hours is that 3 hours of cheapest time?

  30. No this one sadly doesnt work! Tried it just now in the template editor in HA.
    Or am I missing something?

    I did change first hour to 18 (this night today) and last hour to 6 (next morning)
    and by check it manually (SE3) can see that the 3 cheapest hours in row should be tomorrow at around 5 in the morning but it keep giving me next day 18th at 00:00 ?

    1. You can’t set the time between two separate days. Currently it works only with one full day (e.g. 00:00 – 24:00).

      1. Yeha I’m a little stupid sorry. Didnt get that part 😀 Could that be something you would want anyway? I saw nordtveith in his post having something like that. But sadly his code was way wrong for me to edit it right to use in HA.

  31. Hi Toni and everyone else who has contributed with lots of great ideas and code in the comments here. I have got this set up and it’s working great. Also usink Erik’s code for cheapest X hours today/tomorrow.

    I would however also like to try and find the most expensive hours too, so I can turn up heat in the cheapest hours and also turn off/down some things at peak prices. I have tried fiddeling with the loops for finding the cheapest hours, but it’s been too long since I’ve done much coding so I can’t seem to figure it out.

    I started with Erik’s code above and this is what I have so far, but it’s giving me entirely wrong times…

    ————————————————————————————

    – platform: template
    sensors:
    most_expensive_hours_energy_48h:
    device_class: timestamp
    friendly_name: Dyreste periode kommene døgn

    value_template: >
    {%- set numberOfSequentialHours = states(“input_number.timer_dyreste_periode”) | int() -%}
    {%- set lastHour = 24 -%}
    {%- set firstHour = ((as_timestamp(now() ) | timestamp_custom(“%H”)) | int() ) -%}

    {%- if state_attr(“sensor.nordpool_kwh_oslo_nok_3_10_025”, “tomorrow_valid”) == true and now().hour >=13-%}
    {%- set ns = namespace(counter=0, list=[], dearestHour=today_at(“00:00”) + timedelta( hours = (24)), dearestPrice=0.00) -%}
    {%- for i in range(firstHour + numberOfSequentialHours, lastHour +9 ) -%}
    {%- set ns.counter = 0.0 -%}
    {%- for j in range(i-numberOfSequentialHours, i) -%}
    {%- if j ns.dearestPrice -%}
    {%- set ns.dearestPrice = ns.counter -%}
    {%- set ns.dearestHour = today_at(“00:00”) + timedelta( hours = (i – numberOfSequentialHours )) -%}
    {%- endif -%}
    {%- endfor -%}

    {{ ns.dearestHour }}
    {%- set ns.dearestPrice = (ns.dearestPrice / numberOfSequentialHours) -%}
    {%- else -%}
    {%- set ns = namespace(counter=999, list=[], dearestHour=today_at(“00:00”) + timedelta( hours = (24)), dearestPrice=0.00) -%}

    {%- for i in range(firstHour + numberOfSequentialHours, lastHour + 1) -%}
    {%- set ns.counter = 0.0 -%}
    {%- for j in range(i-numberOfSequentialHours, i) -%}
    {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_oslo_nok_3_10_025’, ‘today’)[j] -%}
    {%- endfor -%}

    {%- set ns.list = ns.list + [ns.counter] -%}

    {%- if ns.counter > ns.dearestPrice -%}
    {%- set ns.dearestPrice = ns.counter -%}
    {%- set ns.dearestHour = today_at(“00:00”) + timedelta( hours = (i – numberOfSequentialHours )) -%}
    {%- endif -%}
    {%- endfor -%}

    {{ ns.dearestHour }}
    {%- set ns.dearestPrice = (ns.dearestPrice / numberOfSequentialHours) -%}
    {%- endif -%}

  32. will this work from now ex. time 14.00.00 to the next day at 14.00.00

    or is it from 14.00.00 -> same day at 00.00.00. ?

    1. Original code in the blog post calculating the cheapest hours for tomorrow (and setting start/end times for it). i.e. between 00:00-24:00 next day.

  33. would it be possible to get card to display cheapest hour like 8 hours 20 minuts ?

    1. Hi!

      You should be able to add the timestamp accuracy by using some custom cards (e.g. mushroom card from HACS), but I’m not sure if it’s currently possible by standard HA UI elements.

  34. Highly appreciated thread, very useful! 🙂

    The code posted here is indeed very good for devices where it matters how many time you start and stop them (such as heat pumps), but for dumber things like electric floor heating or electric radiators, I came up with an alternative approach, something like this:

    template:
    – binary_sensor:
    name: cheapest_8_hours_highest_electricity_price
    state: >
    {%- set cheapest_n_hours = 8 -%}
    {%- set ns = namespace(sorted_prices = [], highest_pricepoint = 0.0) -%}
    {%- set ns.sorted_prices = state_attr(‘sensor.nordpool_kwh_se3_sek_3_10_0’, ‘today’)|sort -%}
    {%- set ns.highest_pricepoint = ns.sorted_prices[cheapest_n_hours – 1] -%}
    {{ state_attr(‘sensor.nordpool_kwh_se3_sek_3_10_0’, ‘current_price’) <= ns.highest_pricepoint }}

    I then use the binary sensor to make automations that set the value of thermostats to a slightly higher value when price is low, and a lower value when price is high.

  35. Nihil tweak: ‘snt/KWh’ can be replaced with ‘¢/KWh’ the symbol for cents 🙂

    1. Hi!

      Thank you very much for sharing (and giving me credits on your GitHub page) 🙂
      I’m sure many readers are using the same charging combo as you are.

  36. Ni Toni
    I must say, this was one of the absolutely best and most useful automation i had seen in HA. It give me a better household economy and help balancing the electric power for our future.

    Respect!

    Ole

    1. Great to hear that the automation is useful and thanks for the positive feedback! 🙂

  37. Due to the situation we have in Finland in energy

    This is definitely one of the best of tips and instructions i have seen to date

    I am long time HA user

    Thank you!

  38. Hi Toni, thanks for the code, I have it up and running finally (i am not a developer…so sometimes struggling)

    The reason I am interested in this is because i am trying to find out a system for my battery strategy. Currently, out of the box this can only be done with fixed scheduling on the inverter. I have found various people with al kinds of solutions so far, still looking for the perfect solution and/or getting bits and peaces together in Home Assistant from various examples.

    Main strategy i have in mind:
    Load battery at off peak rates and using the battery at high peak rates.
    And also taking into account solar, if a sunny day is expected, it does not make sense loading the battery for the second peak.

    this means in a nutshell i have some work in progress:
    doing the right calculations for solar, needed batterty charge, calculating peak hours, on and off for peak.
    using automation to send signals to inverter for charge battery and battery first.

    Any comments, remarks, please let me know

    1. Hi!

      Interesting approach, I’m planning of doing similar once I get my solar panels installed this spring. Won’t be having batteries though, but will definitely do some automations regarding current electric prices, solar panel yields and current consumption. I can store some of the excess energy to the car, but unfortunately it can’t be imported back to house 🙂

      1. hi Tony, is it possible to have not 1 but 3 cheapest and expensive time periods in the calculation?
        resulting in 3 sets of sensors.
        I tried myself, but i am not a programmer

        1. Yes of course.

          You should copy the package (and rename) with three different names.
          Also ids of entities inside the package needs to be unique.. so basically renaming like this:
          cheapest_hours_energy_tomorrow_1:
          device_start_time_1:
          device_end_time_1:
          etc… also of course the references for those entities should be renamed.

          Don’t know if that’s any helpful, if you’re not a programmer. 🙂
          I can try to do a sample with two packages for you later this week, if it helps.

  39. Hello,

    I have got this wonderful piece of code working except for the part where it should stop heating.

    I copied the code above but the value for input_datetime.device_end_time seems to be same as for input_datetime.device_start_time

    I have my settings at https://pastebin.com/BzN0ujq0 and can’t really get what I’m doing wrong, Would you please be kind enough to check it?

    Thank you!

    1. Sorry for a slow reply, been quite busy for the last couple of days!

      For a quick look everything seems to be ok, but I might missed something as well.
      I’ll try to do a quick test today afternoon (once the tomorrow prices are available) using your piece of code to see if I can spot the error in there.

  40. This is really cool!

    There’s a simpler way if your heat pump (NIBE) or thermostat (Netatmo, Honeywell, etc.) supports IFTTT. In that case you can use the Stekker.app integration between IFTTT to do the same (trigger based on EPEX price) but without requiring you to write or copy paste any code. Your method is especially valuable if the device is supported by Home Assistant and not Stekker/IFTTT.

    Full disclosure: I’m with Stekker.

    1. Thanks for pointing out your relationship with the Stekker. Even though your comment is more of an ad, your services might actually be useful for someone so I’m allowing this comment 🙂
      Good luck for your business!

    1. Anything is possible. Are you looking for a limit for a single value inside the sequence or just average amount of the total sequence?
      First is a bit more complex and might not suit when using my approach, since this is mostly for sequential hours and if some part of the sequence fails the criteria then there would be two sequences. The latter one should be easily doable if that’s what you are looking.

      Summa summarum: I bit more information is required so I can push you to the right path 🙂

      1. I’d like to have maximum price level ie. it switches only below that set value. But ofcourse could be average too. Idea is to recognize when electricity is really cheap so that it ve dumbed to excess purposes like patio heating etc.

  41. Hi,

    I have tried the code that Erik posted but can’t for the life of me get it to work. I wan’t to know the three cheapest consecutive hours between 22:00 (bedtime) and 06:00 (wake up) when I can charge my mobile, ipad, a power bank, some batteries etc that are all connected to a smart outlet.

    I have made a sensor that concatenates the prices for today and tomorrow which should simplify things A LOT but no luck. Any ideas how I can get it to work with my new sensor for both days? This is what I have:

    – sensor:
    – name: “cheapest_night_period2”
    device_class: timestamp
    state: >
    {%- set numberOfSequentialHours = 3 -%}
    {%- set lastHour = 06 + 24 -%}
    {%- set firstHour = 22 -%}
    {%- if state_attr(‘sensor.nordpool_kwh_se2_sek_4_10_025’, ‘tomorrow_valid’) == true -%}
    {%- set ns = namespace(counter=0, list=[], cheapestHour=today_at(“00:00”) + timedelta( hours = (24)), cheapestPrice=999.00) -%}
    {%- for i in range(firstHour + numberOfSequentialHours, lastHour +9 ) -%}
    {%- set ns.counter = 0.0 -%}
    {%- set ns.counter = ns.counter + states(‘sensor.elpris’)[i] -%}
    {%- set ns.list = ns.list + [ns.counter] -%}
    {%- if ns.counter < ns.cheapestPrice -%}
    {%- set ns.cheapestPrice = ns.counter -%}
    {%- set ns.cheapestHour = states('sensor.elpris')[i] + timedelta( hours = (i-numberOfSequentialHours )) -%}
    {%- endif -%}
    {%- endfor -%}
    {{ ns.cheapestHour }}
    {%- set ns.cheapestPrice = (ns.cheapestPrice / numberOfSequentialHours) -%}
    {%- endif -%}

  42. I’ve got the graphics nicely shown, but now I want to continue, and eventually automate some things.

    You mention “NOTE 2022/09/19: There was a bug in the calculation previously that has been now fixed. If you copied the template before that, please copy the new template below!”

    Now I’m stuck. Where do I copy this template to?

    Thank you for any help on this.

    1. If you’ve just implementing this automation, you should not worry about the “NOTE” -text, since it’s an old issue that is already fixed.
      Just follow the guide step by step 🙂

  43. Sensori cheapest_hours_energy_tomorrow “kaatuu” tunti 3 puuttumiseen siirryttäessä kesäaikaan.

    Virhe tulee riviltä: {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_fi_eur_3_10_024’, ‘tomorrow’)[j] -%}

    Tein purukumipaikan:

    {%- if (j == 3) -%}
    {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_fi_eur_2_10_01’, ‘tomorrow’)[2] -%}
    {%- else -%}
    {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_fi_eur_2_10_01’, ‘tomorrow’)[j] -%}
    {%- endif -%}

  44. Yo. paikkauksessa virhe siis minulla rivillä:

    {%- set ns.counter = ns.counter + state_attr(‘sensor.nordpool_kwh_fi_eur_2_10_01’, ‘tomorrow’)[j] -%}

  45. sequential cheapest hours fails, when np price is null
    here LT hourly prices for 2023-03-26 from http://www.nordpoolgroup.com
    00 – 01 39,66
    01 – 02 39,23
    02 – 03 –
    03 – 04 40,12

    np sensor data
    Raw tomorrow
    – start: ‘2023-03-26T00:00:00+02:00’
    end: ‘2023-03-26T01:00:00+02:00’
    value: 4.037
    – start: ‘2023-03-26T01:00:00+02:00’
    end: ‘2023-03-26T02:00:00+02:00’
    value: 4.799
    – start: ‘2023-03-26T02:00:00+02:00’
    end: ‘2023-03-26T04:00:00+03:00’
    value: 4.747
    – start: ‘2023-03-26T04:00:00+03:00’
    end: ‘2023-03-26T04:00:00+03:00’
    value: null
    – start: ‘2023-03-26T04:00:00+03:00’
    end: ‘2023-03-26T05:00:00+03:00’
    value: 4.855

    1. Yep, will look and fix this out soon, but for the next night (summer time move) I can’t make it

  46. i’ve been using your code since last year, but today suddenly it’s not working anymore and when I do the test using HA Template editor it gives me this error: TypeError: unsupported operand type(s) for +: ‘float’ and ‘NoneType’

    What I did yesterday night was updating HA OS to latest build 10.0 rc1. Would that update change the logic/way we need to write the script?

    1. Hi!

      Most probably the problem is with moving to summer time so everything should work tomorrow again!

      Unfortunately I don’t have time today to fix this issue for the next night 🙂
      Will fix it for the next year though

      1. ah, that’s make sense and no point to fix if the issue only happen once a year 😉

        just wondering if your script also experiencing the same issue today ?

        1. Yes, had the same issue, but didn’t have time to fix it (was on a skiing trip).

  47. I notised that the Nordpool integration stopped working last night. The latest reading is 11:00 march 25. So i suppose it has to do with the summertime setting 00:00. Hopefully it works again tomorrow.

      1. Just to be shure. You mean the sensor.py:

        if not today:
        _LOGGER.debug(“No data for today, unable to set attrs”)
        return

        self._average = mean(today)
        self._min = min(today)
        self._max = max(today)
        self._off_peak_1 = mean(today[0:8])
        self._off_peak_2 = mean(today[20:])
        self._peak = mean(today[8:20])
        self._mean = median(today)

        And change the 7 lines with this?

        self._average = mean(filter(None, today))
        self._min = min(filter(None, today))
        self._max = max(filter(None, today))
        self._off_peak_1 = mean(filter(None, today[0:8]))
        self._off_peak_2 = mean(filter(None, today[20:]))
        self._peak = mean(filter(None, today[8:20]))
        self._mean = median(filter(None, today))

        What does it do? Is it just temporary and i have to change back manually when we go back to winter time? Or is this permanent and switch automatically next time?

        Thanks

        Ole

  48. Thank you very much for adding this post. I haven’t tryed it out as I am what you would call a n00b on coding, but I will dig right into this. Want to share the prices on my page, so people don’t need to pay for it elsewhere.

  49. Many users have problems with the next day Nordpool not load. If i do a restart the next day is loaded. This started to happend when the price is below zero. Any idea or fix?

    1. Hi!

      I’ve noticed the same and unfortunately there’s nothing I can do about it.
      Making an automation to reload the integration every now and then can work as a workaround, but I guess the real problem is within the Nordpool integration (not in my maintenance) or Nordpool server being overloaded.

  50. Toni, I figured out why so many people are having issues with this guide.
    the first lines in your example are
    type: custom:apexcharts-card
    but is should be
    type: ‘custom:apexcharts-card’
    that’s why so may people have problems where it doesn’t find the apexcharts-card.
    Please fix it in your guide, thx

    1. Hi!

      I think hyphens are not mandatory, at least my setup of apex card (using lovelace UI) does not need those.
      Anyhow, it doesn’t hurt if added, so I think it’s a valid point to add those.

      Will fix those to the guide immediately, thanks!

  51. Hi. Great addon, thanks!

    The “now”-marker on the chart can be a bit misleading, since it touches the next hour already after passing nn:30. I have been looking for a way to remove the “now”-marker, and instead make the current hour a different color in the chart, but I haven’t found a way to do that. Anyone else?

  52. Kiitoksia paljon, Toni!
    I am very new to HASS, have been using HomeKit with HomeBridge integration but I needed to get Daikin Altherma 3 domestic hot water heater connected to Nordpool “EE” region prices- success! I am combining your script with a simple automation that checks if price is below certain threshold (somewhere 3-5 cents per kWh, still figuring out) and then turning the water heating on. When price gets higher again, the automation checks if there is an ongoing electricity calendar event and if not then water heating is turned off.
    The combination of cheapest hours and a price threshold seems reasonable to me… for now.

    1. Thank you for your nice comments 🙂

      It’s always good to hear that my blog is helping fellow tinkerers out there!

  53. Hi,

    As the scale of the graph changes from day to day it can vary significantly and confuse members of the family who are not as committed to saving energy. It would be great to have an option to display the days highest and lowest price in the graph. Best would be to display it directly under the heading instead of the price displayed now. Is that something that can be added?

    1. Should work just fine (works on my machine at least). Ensure your Nord Pool integration is fully working 🙂

      1. Same here, had no day ahead last week. Today again: no day ahead prices and -after restart- nothing from nordpool at all…

      2. Waited a little longer. Today’s prices are there now. Day ahead prices remain empty… Haven’t changed anything in HA for the last couple of days.

  54. hello, wow great work.
    but unfortunately i’m a beginner, and i’m a bit stuck.
    I have completed the apex charts.
    But where and how do I have to do the rest?
    Helpers, automation??? In the config.yaml file?

    Thanks in advance

    1. Hi Andreas!

      My current implementations uses packages, so easiest way would be to use those.

      If you don’t know how to edit files inside Home Assistant, you could use something like file editor add-on: https://github.com/home-assistant/addons/blob/master/configurator/README.md

      Next get to know to packages: https://www.home-assistant.io/docs/configuration/packages/
      Packages can contain multiple type of entities, like automations, templated sensors, scripts etc..

      Finally get the proper package sample from https://github.com/kotope/ha_nordpool_cheapest_hours (and read the corresponding blog article) and modify it to suit your needs 🙂

  55. Hei,

    Jos olen tyhmä enkä ymmärrä koko homeassistantista mitään, niin miten saan tämän toimimaan? Olen tapellut tunteja tämän kanssa enkä ymmärrä miten saada edes koko automaatiota toimimaan.

  56. Thanks for the effort done here!

    I have a doubt.
    Im also used ApexCharts to display the graph, but as I try to use the the now function, and also show current hours price in the heading, im getting the marker and the price to be one hour ahead.
    So currently the price is 8 Öre/kwh, but showing the hour for 1900-2000 CET.

    Do you know how to get it to show current CET time, and not GMT (which i guessing it is showing..)

    Regards
    Anders

  57. Hello,. For some reason, looks like (or guess) the move to winter time messed up the operation. What could be the reason? Any ideas? It has been working for about a year or so but last worked on the day the clocks turned back 26-27.10.24, and not since…

  58. https://www.home-assistant.io/integrations/nordpool
    I tried this simple version from here, but do not know how to handle different day and night time extra costs. So I have to pay some extra/kwh and night time this is cheaper. I am quite inexperienced programmer so if there is simple answer I appreciate 🙂
    So my price is energy price + transfer price for night and day + marginal.
    Thanks

    1. That’s new Home Assistant official Nord Pool integration that came live with 2024.12 release.
      As far as I know, you can’t set custom costs using that.

      This article is written using custom Nord Pool integration for Home Assistant (https://github.com/custom-components/nordpool) that supports extra costs and other nice features as well.
      To be honest, the official Home Assistant Nord Pool integration at it’s current state isn’t quite there yet 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Buy Me A Coffee