Blueprint that uses Nordpool and lets you turn on devices on the cheapest hours and make automations based on that information

what is the sensor that one is adding? (i get this is probably super basic but I just can not get this to work) is it the data with tomorrow? if so can someone please share theirs that works with this blueprint?

===========================
detail

Summary

I recently installed the nord pool integration (link below) and got loads of sensors but nothing about tomorrow… so as this is checking in the future, I guess we need tomorrow’s data …

so I created the sensor from the script here:
Nord Pool - Home Assistant

which outputs like this in developer>states>attributes> :

data: 
- start: '2025-02-28 00:00:00'
  end: '2025-02-28 01:00:00'
  price: 0.31163
- start: '2025-02-28 01:00:00'
  end: '2025-02-28 02:00:00'
  price: 0.30168
........ etc....etc...

but the blueprint (and the original script fails as it seems to look for something else (?)

also, regarding the blueprint if i don’t an entity to drive with it it errors:
“Message malformed: Missing input entity_id_to_flip”
… maybe basic, but I just wanted to know when the times are … and had hoped to trigger them later based on this (?) – could it be that it if it is blank it creates a binary switch?

Finally, as I had started with Toni’s script and failed so I started to debug and during debugging in the template to the entity here:

sensor: sensor.nordpool_kwh_fi_eur_3_10_024

it seems I need to add ‘states.’ to it to get it to output, like this when I was testing it in the in developer tools > template:

{% set this = states.sensor.nord_pool_se3_tomorrow_lowest_price %}
{%- set sensor = (this.attributes.get('sensor', 'sensor.nord_pool_se3_tomorrow_lowest_price') | string) -%}
sensor: {{sensor}}

so… it go me thinking … what exactly does this and toni’s script and this blueprint expect for sensor ?

seems I have gone off into the weeds a bit … it shouldn’t be this hard should it? any help is appreciated

The blueprint doesn’t handle daylight saving, it assumes the nordpool has an array of 24 items. Today it only has 23, it should probably just look for the last item rather than assuming the length.

Suggested improvement to handle day light saving:

Change this line:

{% set diff = sorted_prices[23] - sorted_prices[0] %} 

To:

{% set diff = sorted_prices[-1] - sorted_prices[0] %} 

Explanation:
Sorted_prices[23] assumes there are 24 items. Automation fails when there are only 23 items during day light saving switch in spring (northern hemisphere), and will miss the the most expensive hour when there are 25 items during daylight saving switch in fall.
Sorted_prices[-1] always find the last items in the list.

What is the Grid area sensor? should that be the current price or something ells?

I have the same question, I tried this blueprint this weekend but it won´t run as expected.
Which value from nordpool should I use?

In the near future, Nordpool will begin with 15-minute prices. Will that break the automation? At least Vattenfall will stop with 1-hour pricing, so doing nothing is not an option if that is the case.

I had the Nordpool integration that is availiable as a “standard” integration. I changed integration from that to the Nordpool in HACS. After that i used the sensor “nordpool_kwh_se3_sek”. Now it works fine.

Hi

Thanks to all for your time and effort. For helping others.

I would like to activate “Solar Sell” in my solar inverter for 5 most expensive hours in Nordpool per day.
What do you think, can I use this blueprint or what needs changing?

br
K

Sorry for being dumb. I set Always On price to 0.1 , Always Off price to 0.2. Current price is 0.01 and when I test the blueprint it turns off the relay. What I am doing wrong?

Hi,
I’ve been using this automation for a while now, however, Nordpool has now switched to 15-minute interval on the spot price.
I have tried to combine and average the spot price into 1 hour slots to make this work, but seems I’m unable to do so so that timestamps would also be correct.

Does anyone have idea how to make it work so that this automation would still be functional?

I was looking at the same last night and made some edits locally to my blueprint, I bet someone could do something similar in a PR to the blueprint directly to avoid manual edits.

Right now the template assumes 24 prices per day (hourly) however since the switch to 15-minute prices, home assistant/nordpool integration receives 96 prices (15-minute intervals). Due to this a few parts of the logic in the value_template breaks.

{% set price = prices[h] %} only works if prices[h] has 24 values, with 96 we’ll have to account for both now().hour and now().minute:

{% set idx = h * 4 + (m // 15) %}
{% set price = prices[idx] %}

Range / diff / ratio calculations has hardcoded the last hour to be [23], better to use [-1] as the last element instead. Also change above_low to utilise already defined price variable:

{% set above_low = price - sorted_prices[0] %}
{% set diff = sorted_prices[-1] - sorted_prices[0] %}

Also note, the hour_rank variable, i.e number of hours you’ve set your equipment to be on, will now equate to number of 15-minutes. What previously was 5 hours using this variable would be 15*5 = 1h15m instead. For now I’ve just opted to use 20 here to equal my previous 5 hours in the settings, you could also change the slider range and friendly name to reflect quarters.

Cleaned up version with the changes:

value_template: >-
  {% set prices = state_attr(grid_area_var, 'today') | list %}
  {% set h = now().hour %}
  {% set m = now().minute %}
  {% set idx = h * 4 + (m // 15) %}
  {% set price = prices[idx] %}
  {% set sorted_prices = prices | sort %}
  {% set above_low = price - sorted_prices[0] %}
  {% set diff = sorted_prices[-1] - sorted_prices[0] %}
  {% set ratio = ((above_low / diff) * 100) | round %}
  {% set rank = sorted_prices.index(price) + 1 %}
  {% set seton = 
     (m | int == start_minute_var | int and start_minute_var | int != end_minute_var | int)
     or price | float <= allways_on_var | float
     or ratio | int < hour_ratio_var | int
     or rank | int <= hour_rank_var | int
  %}
  {{ price | float < allways_off_var | float and seton }}

Amazing work, I will look into implementing the same changes later myself.
Will be a nice excecise to stab a blueprint, haven’t done that before… :smile:

First: I am no software programmer, but Grok is… After a little talking with Grok where I sent the old blueprint from Mikael and my automation and script YAML files to Grok it came up with some alternatives. Seems to work great.

Use File editor or similar to put this code in a file in this location. (or in a folder) /homeassistant/blueprints/automation/nordpool_15min_switch_control.yaml
Remember the name of your old automation and delete it. Then create new with this Blueprint instead and give it the same name, if you have something using it.
I have this on 2 locations now and seems to work just fine.

It does not have Minimum hours, but give the code to Grok and it will create a solution.

Here is the code:

blueprint:
  name: Nordpool 15-Minute Price-Based Actions
  description: Automates any actions (e.g., scripts, services) based on Nordpool 15-minute price intervals to optimize energy use.
  domain: automation
  input:
    nordpool_sensor:
      name: Nordpool Sensor
      description: The Nordpool sensor entity providing raw_today with 15-minute prices.
      selector:
        entity:
          domain: sensor
          integration: nordpool
    turn_on_actions:
      name: Turn On Actions
      description: Actions to run when conditions for turning on are met (e.g., call a script or turn on devices).
      default: []
      selector:
        action: {}
    turn_off_actions:
      name: Turn Off Actions
      description: Actions to run when conditions for turning off are met (e.g., turn off switches).
      default: []
      selector:
        action: {}
    interval_rank:
      name: Cheapest Intervals per Day
      description: Number of 15-minute intervals (e.g., 40 = ~10 hours) to consider "cheap" for turning on.
      default: 40
      selector:
        number:
          min: 0
          max: 96
          step: 1
          mode: slider
    price_ratio:
      name: Price Ratio (%)
      description: Max % of the day's price range to consider "cheap" (e.g., bottom 30%).
      default: 30
      selector:
        number:
          min: 0
          max: 100
          step: 5
          mode: slider
    always_on_threshold:
      name: Always On Price Threshold
      description: Run turn-on actions if price ≤ this value (ignores rank/ratio).
      default: 0.25
      selector:
        number:
          min: 0.0
          max: 20.0
          step: 0.1
          mode: slider
    always_off_threshold:
      name: Always Off Price Threshold
      description: Run turn-off actions if price ≥ this value (ignores rank/ratio).
      default: 1.5
      selector:
        number:
          min: 0.0
          max: 20.0
          step: 0.1
          mode: slider

variables:
  nordpool_sensor_var: !input nordpool_sensor
  interval_rank_var: !input interval_rank
  price_ratio_var: !input price_ratio
  always_on_var: !input always_on_threshold
  always_off_var: !input always_off_threshold

trigger:
  - platform: time_pattern
    minutes: /15

action:
  - variables:
      prices: "{{ state_attr(nordpool_sensor_var, 'raw_today') | map(attribute='value') | list | default([]) }}"
      interval_index: "{{ (now().hour * 4) + (now().minute // 15) }}"
      price: "{{ prices[interval_index] | float(0) if prices | length > interval_index else 0 }}"
      sorted_prices: "{{ prices | sort | default([]) }}"
      above_low: "{{ price - sorted_prices[0] if sorted_prices else 0 }}"
      diff: "{{ sorted_prices[-1] - sorted_prices[0] if sorted_prices else 0 }}"
      ratio: "{{ (above_low / diff * 100) | round(0) if diff > 0 else 0 }}"
      rank: "{{ sorted_prices.index(price) + 1 if price in sorted_prices else 999 }}"
      is_cheap: "{{ price <= always_on_var or ratio <= price_ratio_var or rank <= interval_rank_var }}"
  - choose:
      - conditions:
          - "{{ prices | length > 0 }}"  # Ensure data is available
          - "{{ price < always_off_var and is_cheap }}"
        sequence: !input turn_on_actions
      - conditions:
          - "{{ prices | length > 0 }}"  # Ensure data is available
          - "{{ price >= always_off_var or not is_cheap }}"
        sequence: !input turn_off_actions
    default: []  # Do nothing if no data
mode: single

Thanks for a great input! :slight_smile:

In the first choose/condition, second line, shouldn’t it be always_on_var instead?

I have no idea if it should be always_on_ or always_off_. It works just fine and has been doing the right things for some days now in two locations. One in Norway and one in Sweden.

I have also modified it with minimum hours.

It controls a script for turning on/off L1, L2 and L3 in my cabin using Shelly equipment.

blueprint:
  name: Nordpool 15-Minute Price-Based Actions
  description: Automates actions based on Nordpool 15-minute price intervals to optimize energy use, with a minimum daily "on" time.
  domain: automation
  input:
    nordpool_sensor:
      name: Nordpool Sensor
      description: The Nordpool sensor entity providing raw_today with 15-minute prices.
      selector:
        entity:
          domain: sensor
          integration: nordpool
    turn_on_actions:
      name: Turn On Actions
      description: Actions to run when conditions for turning on are met (e.g., call a script or turn on devices).
      default: []
      selector:
        action: {}
    turn_off_actions:
      name: Turn Off Actions
      description: Actions to run when conditions for turning off are met (e.g., turn off switches).
      default: []
      selector:
        action: {}
    interval_rank:
      name: Cheapest Intervals per Day
      description: Number of 15-minute intervals (e.g., 40 = ~10 hours) to consider "cheap" for turning on.
      default: 40
      selector:
        number:
          min: 0
          max: 96
          step: 1
          mode: slider
    price_ratio:
      name: Price Ratio (%)
      description: Max % of the day's price range to consider "cheap" (e.g., bottom 30%).
      default: 30
      selector:
        number:
          min: 0
          max: 100
          step: 5
          mode: slider
    always_on_threshold:
      name: Always On Price Threshold
      description: Run turn-on actions if price ≤ this value (ignores rank/ratio).
      default: 0.25
      selector:
        number:
          min: 0.0
          max: 20.0
          step: 0.1
          mode: slider
    always_off_threshold:
      name: Always Off Price Threshold
      description: Run turn-off actions if price ≥ this value (ignores rank/ratio).
      default: 1.5
      selector:
        number:
          min: 0.0
          max: 20.0
          step: 0.1
          mode: slider
    hour_rank:  # NEW INPUT
      name: Hours On
      description: Set the minimum number of hours per day that devices should be on.
      default: 12
      selector:
        number:
          min: 0.0
          max: 24.0
          mode: slider
          step: 1.0

variables:
  nordpool_sensor_var: !input nordpool_sensor
  interval_rank_var: !input interval_rank
  price_ratio_var: !input price_ratio
  always_on_var: !input always_on_threshold
  always_off_var: !input always_off_threshold
  hour_rank_var: !input hour_rank  # NEW VARIABLE
  min_intervals: "{{ (hour_rank_var * 4) | round(0) }}"  # Convert hours to 15-minute intervals (4 per hour)

trigger:
  - platform: time_pattern
    minutes: /15

action:
  - variables:
      prices: "{{ state_attr(nordpool_sensor_var, 'raw_today') | map(attribute='value') | list | default([]) }}"
      interval_index: "{{ (now().hour * 4) + (now().minute // 15) }}"
      price: "{{ prices[interval_index] | float(0) if prices | length > interval_index else 0 }}"
      sorted_prices: "{{ prices | sort | default([]) }}"
      above_low: "{{ price - sorted_prices[0] if sorted_prices else 0 }}"
      diff: "{{ sorted_prices[-1] - sorted_prices[0] if sorted_prices else 0 }}"
      ratio: "{{ (above_low / diff * 100) | round(0) if diff > 0 else 0 }}"
      rank: "{{ sorted_prices.index(price) + 1 if price in sorted_prices else 999 }}"
      effective_rank: "{{ [interval_rank_var, min_intervals] | max }}"  # Use the higher of interval_rank or min_intervals
      is_cheap: "{{ price <= always_on_var or ratio <= price_ratio_var or rank <= effective_rank }}"
  - choose:
      - conditions:
          - "{{ prices | length > 0 }}"  # Ensure data is available
          - "{{ price < always_off_var and is_cheap }}"
        sequence: !input turn_on_actions
      - conditions:
          - "{{ prices | length > 0 }}"  # Ensure data is available
          - "{{ price >= always_off_var or not is_cheap }}"
        sequence: !input turn_off_actions
    default: []  # Do nothing if no data
mode: single
1 Like

I have now updated the blueprints and they now support 15 minute prices.
And fixed a few issues.

2 Likes

I also have a use case for this.
Is it much work to change the blueprint so that it works in the opposite way?
activating an action during the 5 most expensive hours?

Hi

I’m using Nordpool Core, and I can’t find a useful sensor. I found the nordpool.get_prices_for_date but that’s an action. Can anyone point me in the direktion to create a sensor that works ?