Itse olen tykästynyt tekemään pösä ohjaukset klo 18-17:45 aikaikkunassa, koska tällöin ei vuorokauden kahtapuolen olevat halvat tunnit sekoita ohjauksia, sekä aurinkovoimalan tuotosta on tieto ennen uuden jakson alkua.
Lisäksi pidän siitä että hintojen lisäksi on näkyvissä jakson rank tieto.
kolmantena asiana itselle tuli myös kausisiirron 3.5snt/kWh lisä 1.11-31.3 ma-la 07-22 jaksolle, mikä myös tulee ottaa huomioon rank ja hinta kalkuloinnissa. Tietenkin haluan että huomioi automaattisesti kausisiirron päivähinnan alkamisen ja poistumisen.
Näiden perusteella oon saanut käyttöön kaikki edellä luetellut asiat toteuttavan HA template ja ohjaus hässäkän
Jaksot on saatavana 15min jaksona, sekä 30min keskiarvona ja 1h keskiarvona
Koodi on tehty muotoon että sen voi liittää erillisenä pakettina kuten @Temezin tekemä loistava ohjauskokonaisuus on myös tehty. Ja Rank & price ohjaukset on suoraan pöllitty hänen mallistaan - kiitos siitä
Itsellä ei kovin kummoisia dashboard kilkkeitä ole tehtynä, mutta käytännössä vain current & next period näkymät.
Joista laitan esiin vain 1h listat


Dashboard listan esittämiseen pitää hakea ja asentaa HACSista flex-table-card
Next period näkymän saa yo.koodilla kun vaihtaa current_period -> next_period_preview
sama taulukkokoodi toimii kaikkiin 15min, 30min ja 1h versioihin vain sensorin (sensor.nordpool_1h_ranked_18_17) nimeä muuttamalla
15min sensor.nordpool_15min_ranked_18_1745
30min sensor.nordpool_30min_ranked_18_1730
Lisäksi pidän siitä että hintojen lisäksi on näkyvissä jakson rank tieto.
kolmantena asiana itselle tuli myös kausisiirron 3.5snt/kWh lisä 1.11-31.3 ma-la 07-22 jaksolle, mikä myös tulee ottaa huomioon rank ja hinta kalkuloinnissa. Tietenkin haluan että huomioi automaattisesti kausisiirron päivähinnan alkamisen ja poistumisen.
Näiden perusteella oon saanut käyttöön kaikki edellä luetellut asiat toteuttavan HA template ja ohjaus hässäkän
Jaksot on saatavana 15min jaksona, sekä 30min keskiarvona ja 1h keskiarvona
Koodi on tehty muotoon että sen voi liittää erillisenä pakettina kuten @Temezin tekemä loistava ohjauskokonaisuus on myös tehty. Ja Rank & price ohjaukset on suoraan pöllitty hänen mallistaan - kiitos siitä
YAML:
# ========================================
# Nordpool 18:00-17:45 Price Templates
# ========================================
# Features:
# - Seasonal pricing (Nov 1 - Mar 31, Mon-Sat 7-22: +3.5 cents/kWh)
# - 15min, 30min and 1h average prices
# - Ranked prices
# - Fixed update_next logic (preview 15:00-18:00)
# ========================================
# ========================================
# INPUT BOOLEAN - Seasonal Pricing
# ========================================
input_boolean:
nordpool_winter_pricing:
name: Nordpool Winter Pricing
icon: mdi:snowflake
# ========================================
# AUTOMATIONS - Season Changes
# ========================================
automation:
# Activate winter pricing on Nov 1
- id: nordpool_winter_pricing_on
alias: "Nordpool: Activate winter pricing"
trigger:
- platform: template
value_template: >-
{{ now().month == 11 and now().day == 1 and now().hour == 0 and now().minute == 0 }}
action:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.nordpool_winter_pricing
mode: single
# Deactivate winter pricing on Apr 1
- id: nordpool_winter_pricing_off
alias: "Nordpool: Deactivate winter pricing"
trigger:
- platform: template
value_template: >-
{{ now().month == 4 and now().day == 1 and now().hour == 0 and now().minute == 0 }}
action:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.nordpool_winter_pricing
mode: single
# Check seasonal pricing on Home Assistant startup
- id: nordpool_check_season_on_start
alias: "Nordpool: Check season on startup"
trigger:
- platform: homeassistant
event: start
action:
- service: >-
{% set month = now().month %}
{% if month >= 11 or month <= 3 %}
input_boolean.turn_on
{% else %}
input_boolean.turn_off
{% endif %}
target:
entity_id: input_boolean.nordpool_winter_pricing
mode: single
# ========================================
# TEMPLATE SENSORS
# ========================================
template:
# ========================================
# BASE 15MIN SENSOR (SEASONALLY ADJUSTED PRICES)
# ========================================
# This sensor calculates all 15min prices with seasonal adjustment
# and serves as the base for other sensors
# ========================================
- triggers:
- trigger: state
entity_id: sensor.nordpool
attribute: raw_tomorrow
id: "update_preview"
- trigger: time
at: "18:00:00"
id: "update_current"
- trigger: time
at: "15:00:00"
id: "update_preview"
- trigger: homeassistant
event: start
id: "update_current"
sensor:
- name: "Nordpool Base 15min Prices"
unique_id: nordpool_base_15min_prices
state: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set current_minute = (now_time.minute // 15) * 15 %}
{% set search_time = now_time.strftime('%Y-%m-%d') + ' %02d:%02d' | format(now_time.hour, current_minute) %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['price_adjusted'] | float | round(4) }}
{% else %}
{# Jos ei löydy nykyistä aikaa, kokeile suoraan sensor.nordpool #}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
{% else %}
{# Jos current_period on tyhjä, käytä suoraan Nordpool-sensoria #}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
unit_of_measurement: "c/kWh"
device_class: monetary
icon: mdi:flash
attributes:
# Current period (18:00-17:45)
current_period: >-
{% if trigger and trigger.id == 'update_current' %}
{% set today_prices = state_attr('sensor.nordpool', 'raw_today') %}
{% set tomorrow_prices = state_attr('sensor.nordpool', 'raw_tomorrow') %}
{% if today_prices and tomorrow_prices %}
{% set today_filtered = today_prices | selectattr('start.hour', '>=', 18) | list %}
{% set tomorrow_filtered = tomorrow_prices | selectattr('start.hour', '<=', 17) | list %}
{% set combined = today_filtered + tomorrow_filtered %}
{# Seasonal pricing status #}
{% set winter_pricing = is_state('input_boolean.nordpool_winter_pricing', 'on') %}
{% set winter_surcharge = 3.5 %}
{% set ns = namespace(result=[]) %}
{% for item in combined %}
{# Calculate seasonally adjusted price #}
{% set base_price = item.value %}
{% set hour = item.start.hour %}
{% set weekday = item.start.weekday() %}
{# Winter season Mon-Sat 7-22 (weekday: Mon=0, Sun=6) #}
{% if winter_pricing and weekday < 6 and hour >= 7 and hour < 22 %}
{% set adjusted_price = base_price + winter_surcharge %}
{% else %}
{% set adjusted_price = base_price %}
{% endif %}
{% set ns.result = ns.result + [{
'time': item.start.strftime('%Y-%m-%d %H:%M'),
'price_base': base_price | round(4),
'price_adjusted': adjusted_price | round(4),
'hour': hour,
'weekday': weekday
}] %}
{% endfor %}
{{ ns.result }}
{% else %}
{# If data not available, keep old data #}
{{ this.attributes.get('current_period', []) }}
{% endif %}
{% else %}
{{ this.attributes.get('current_period', []) }}
{% endif %}
# Next period preview (TODAY 18:00-23:45 + TOMORROW 00:00-17:45)
# Updates at 15:00 when tomorrow's prices are available
next_period_preview: >-
{% if trigger and trigger.id == 'update_preview' %}
{% set today_prices = state_attr('sensor.nordpool', 'raw_today') %}
{% set tomorrow_prices = state_attr('sensor.nordpool', 'raw_tomorrow') %}
{% if today_prices and tomorrow_prices %}
{# TODAY 18:00-23:45 - start of next period! #}
{% set today_filtered = today_prices | selectattr('start.hour', '>=', 18) | list %}
{# TOMORROW 00:00-17:45 - end of next period! #}
{% set tomorrow_filtered = tomorrow_prices | selectattr('start.hour', '<=', 17) | list %}
{% set combined = today_filtered + tomorrow_filtered %}
{# Kausihinnoittelun tila #}
{% set winter_pricing = is_state('input_boolean.nordpool_winter_pricing', 'on') %}
{% set winter_surcharge = 3.5 %}
{% set ns = namespace(result=[]) %}
{% for item in combined %}
{% set base_price = item.value %}
{% set hour = item.start.hour %}
{% set weekday = item.start.weekday() %}
{% if winter_pricing and weekday < 6 and hour >= 7 and hour < 22 %}
{% set adjusted_price = base_price + winter_surcharge %}
{% else %}
{% set adjusted_price = base_price %}
{% endif %}
{% set ns.result = ns.result + [{
'time': item.start.strftime('%Y-%m-%d %H:%M'),
'price_base': base_price | round(4),
'price_adjusted': adjusted_price | round(4),
'hour': hour,
'weekday': weekday,
'preview': true
}] %}
{% endfor %}
{{ ns.result }}
{% else %}
{# If data not available, keep old data #}
{{ this.attributes.get('next_period_preview', []) }}
{% endif %}
{% else %}
{{ this.attributes.get('next_period_preview', []) }}
{% endif %}
# ========================================
# 15MIN RANKED SENSOR
# ========================================
# Uses base sensor and adds rank information
# ========================================
- triggers:
- trigger: state
entity_id: sensor.nordpool_base_15min_prices
- trigger: time_pattern
minutes: "/15"
sensor:
- name: "Nordpool 15min Ranked 18-1745"
unique_id: nordpool_15min_ranked_18_1745
state: >-
{% set base = states('sensor.nordpool_base_15min_prices') %}
{% if base not in ['unavailable', 'unknown'] %}
{{ base | float }}
{% else %}
unavailable
{% endif %}
unit_of_measurement: "c/kWh"
device_class: monetary
icon: mdi:flash
attributes:
# Current period with ranks
current_period: >-
{% set periods = state_attr('sensor.nordpool_base_15min_prices', 'current_period') %}
{% if periods %}
{# Sort prices for ranking #}
{% set sorted_periods = periods | sort(attribute='price_adjusted') %}
{# Add rank information #}
{% set ns = namespace(result=[]) %}
{% for item in periods %}
{% set rank = (sorted_periods | selectattr('time', '==', item.time) | list)[0] %}
{% set rank_num = sorted_periods.index(rank) + 1 %}
{% set ns.result = ns.result + [{
'time': item.time,
'price': item.price_adjusted,
'price_base': item.price_base,
'rank': rank_num
}] %}
{% endfor %}
{{ ns.result }}
{% else %}
[]
{% endif %}
# Preview of next period (15:00-18:00)
next_period_preview: >-
{% set periods = state_attr('sensor.nordpool_base_15min_prices', 'next_period_preview') %}
{% if periods %}
{% set sorted_periods = periods | sort(attribute='price_adjusted') %}
{% set ns = namespace(result=[]) %}
{% for item in periods %}
{% set rank = (sorted_periods | selectattr('time', '==', item.time) | list)[0] %}
{% set rank_num = sorted_periods.index(rank) + 1 %}
{% set ns.result = ns.result + [{
'time': item.time,
'price': item.price_adjusted,
'price_base': item.price_base,
'rank': rank_num,
'preview': true
}] %}
{% endfor %}
{{ ns.result }}
{% else %}
[]
{% endif %}
# Current rank
current_rank: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set current_minute = (now_time.minute // 15) * 15 %}
{% set search_time = now_time.strftime('%Y-%m-%d') + ' %02d:%02d' | format(now_time.hour, current_minute) %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['rank'] }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
# ========================================
# 30MIN RANKED SENSOR
# ========================================
# Uses 15min data and calculates averages
# ========================================
- triggers:
- trigger: state
entity_id: sensor.nordpool_15min_ranked_18_1745
- trigger: time_pattern
minutes: "/30"
- trigger: homeassistant
event: start
sensor:
- name: "Nordpool 30min Ranked 18-1730"
unique_id: nordpool_30min_ranked_18_1730
state: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set current_minute = (now_time.minute // 30) * 30 %}
{% set search_time = now_time.strftime('%Y-%m-%d') + ' %02d:%02d' | format(now_time.hour, current_minute) %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['price'] | float | round(4) }}
{% else %}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
{% else %}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
unit_of_measurement: "c/kWh"
device_class: monetary
icon: mdi:flash
attributes:
current_period: >-
{% set periods_15min = state_attr('sensor.nordpool_15min_ranked_18_1745', 'current_period') %}
{% if periods_15min %}
{% set ns = namespace(periods_30min=[]) %}
{# Process 15min periods in pairs #}
{% for i in range(0, periods_15min|length, 2) %}
{% if i + 1 < periods_15min|length %}
{% set period1 = periods_15min[i] %}
{% set period2 = periods_15min[i + 1] %}
{% set avg_price = ((period1['price'] + period2['price']) / 2) | round(4) %}
{% set ns.periods_30min = ns.periods_30min + [{
'time': period1['time'],
'price': avg_price
}] %}
{% endif %}
{% endfor %}
{# Sort and rank #}
{% set sorted_30min = ns.periods_30min | sort(attribute='price') %}
{% set ns2 = namespace(result=[]) %}
{% for item in ns.periods_30min %}
{% set rank = (sorted_30min | selectattr('time', '==', item['time']) | list)[0] %}
{% set rank_num = sorted_30min.index(rank) + 1 %}
{% set ns2.result = ns2.result + [{
'time': item['time'],
'price': item['price'],
'rank': rank_num
}] %}
{% endfor %}
{{ ns2.result }}
{% else %}
[]
{% endif %}
next_period_preview: >-
{% set periods_15min = state_attr('sensor.nordpool_15min_ranked_18_1745', 'next_period_preview') %}
{% if periods_15min %}
{% set ns = namespace(periods_30min=[]) %}
{% for i in range(0, periods_15min|length, 2) %}
{% if i + 1 < periods_15min|length %}
{% set period1 = periods_15min[i] %}
{% set period2 = periods_15min[i + 1] %}
{% set avg_price = ((period1['price'] + period2['price']) / 2) | round(4) %}
{% set ns.periods_30min = ns.periods_30min + [{
'time': period1['time'],
'price': avg_price,
'preview': true
}] %}
{% endif %}
{% endfor %}
{% set sorted_30min = ns.periods_30min | sort(attribute='price') %}
{% set ns2 = namespace(result=[]) %}
{% for item in ns.periods_30min %}
{% set rank = (sorted_30min | selectattr('time', '==', item['time']) | list)[0] %}
{% set rank_num = sorted_30min.index(rank) + 1 %}
{% set ns2.result = ns2.result + [{
'time': item['time'],
'price': item['price'],
'rank': rank_num,
'preview': true
}] %}
{% endfor %}
{{ ns2.result }}
{% else %}
[]
{% endif %}
current_rank: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set current_minute = (now_time.minute // 30) * 30 %}
{% set search_time = now_time.strftime('%Y-%m-%d') + ' %02d:%02d' | format(now_time.hour, current_minute) %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['rank'] }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
# ========================================
# 1H RANKED SENSOR
# ========================================
# Uses 15min data and calculates 1h averages
# ========================================
- triggers:
- trigger: state
entity_id: sensor.nordpool_15min_ranked_18_1745
- trigger: time_pattern
hours: "*"
minutes: 0
- trigger: homeassistant
event: start
sensor:
- name: "Nordpool 1h Ranked 18-17"
unique_id: nordpool_1h_ranked_18_17
state: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set search_time = now_time.strftime('%Y-%m-%d %H:00') %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['price'] | float | round(4) }}
{% else %}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
{% else %}
{{ state_attr('sensor.nordpool', 'current_price') | float(0) | round(4) }}
{% endif %}
unit_of_measurement: "c/kWh"
device_class: monetary
icon: mdi:flash
attributes:
current_period: >-
{% set periods_15min = state_attr('sensor.nordpool_15min_ranked_18_1745', 'current_period') %}
{% if periods_15min %}
{% set ns = namespace(periods_1h=[]) %}
{# Process 15min periods in groups of 4 (1h) #}
{% for i in range(0, periods_15min|length, 4) %}
{% if i + 3 < periods_15min|length %}
{% set period1 = periods_15min[i] %}
{% set period2 = periods_15min[i + 1] %}
{% set period3 = periods_15min[i + 2] %}
{% set period4 = periods_15min[i + 3] %}
{% set avg_price = ((period1['price'] + period2['price'] + period3['price'] + period4['price']) / 4) | round(4) %}
{% set ns.periods_1h = ns.periods_1h + [{
'time': period1['time'],
'price': avg_price
}] %}
{% endif %}
{% endfor %}
{# Sort and rank #}
{% set sorted_1h = ns.periods_1h | sort(attribute='price') %}
{% set ns2 = namespace(result=[]) %}
{% for item in ns.periods_1h %}
{% set rank = (sorted_1h | selectattr('time', '==', item['time']) | list)[0] %}
{% set rank_num = sorted_1h.index(rank) + 1 %}
{% set ns2.result = ns2.result + [{
'time': item['time'],
'price': item['price'],
'rank': rank_num
}] %}
{% endfor %}
{{ ns2.result }}
{% else %}
[]
{% endif %}
next_period_preview: >-
{% set periods_15min = state_attr('sensor.nordpool_15min_ranked_18_1745', 'next_period_preview') %}
{% if periods_15min %}
{% set ns = namespace(periods_1h=[]) %}
{% for i in range(0, periods_15min|length, 4) %}
{% if i + 3 < periods_15min|length %}
{% set period1 = periods_15min[i] %}
{% set period2 = periods_15min[i + 1] %}
{% set period3 = periods_15min[i + 2] %}
{% set period4 = periods_15min[i + 3] %}
{% set avg_price = ((period1['price'] + period2['price'] + period3['price'] + period4['price']) / 4) | round(4) %}
{% set ns.periods_1h = ns.periods_1h + [{
'time': period1['time'],
'price': avg_price,
'preview': true
}] %}
{% endif %}
{% endfor %}
{% set sorted_1h = ns.periods_1h | sort(attribute='price') %}
{% set ns2 = namespace(result=[]) %}
{% for item in ns.periods_1h %}
{% set rank = (sorted_1h | selectattr('time', '==', item['time']) | list)[0] %}
{% set rank_num = sorted_1h.index(rank) + 1 %}
{% set ns2.result = ns2.result + [{
'time': item['time'],
'price': item['price'],
'rank': rank_num,
'preview': true
}] %}
{% endfor %}
{{ ns2.result }}
{% else %}
[]
{% endif %}
current_rank: >-
{% set periods = this.attributes.get('current_period', []) %}
{% if periods %}
{% set now_time = now() %}
{% set search_time = now_time.strftime('%Y-%m-%d %H:00') %}
{% set current = periods | selectattr('time', '==', search_time) | list %}
{% if current %}
{{ current[0]['rank'] }}
{% else %}
unavailable
{% endif %}
{% else %}
unavailable
{% endif %}
# ========================================
# RANK & PLAN TEMPLATES
# ========================================
# Now uses 1h average prices
# change ordpool_1h_ranked_18_17 sensor name as follow:
# 15min control -> nordpool_15min_ranked_18_1745
# 30min control -> nordpool_30min_ranked_18_1730
# or multiply all below sensors to make separate controls
- binary_sensor:
- name: "Rank acceptable"
unique_id: rank_acceptable
device_class: power
availability: '{{ has_value("sensor.nordpool_1h_ranked_18_17") }}'
state: '{{ state_attr("sensor.nordpool_1h_ranked_18_17", "current_rank")| int <= states("input_number.rank_slider") | int }}'
- name: "Price acceptable"
unique_id: price_acceptable
device_class: power
availability: '{{ has_value("sensor.nordpool_1h_ranked_18_17") }}'
state: '{{ states("sensor.nordpool_1h_ranked_18_17") | float <= states("input_number.price_plan_slider") | float }}'
- name: "Price or Rank acceptable"
unique_id: price_or_rank_acceptable
device_class: power
state: '{{ is_state("binary_sensor.rank_acceptable", "on") or is_state("binary_sensor.price_acceptable", "on") }}'
- name: "Price and Rank acceptable"
unique_id: price_and_rank_acceptable
device_class: power
state: '{{ is_state("binary_sensor.rank_acceptable", "on") and is_state("binary_sensor.price_acceptable", "on") }}'
- sensor:
- name: "Price Rank plan"
unique_id: price_rank_plan
state: >
{% set mapper = {
"price": "price_acceptable",
"rank": "rank_acceptable",
"price or rank": "price_or_rank_acceptable",
"price and rank": "price_and_rank_acceptable" } %}
{% set state = states("input_select.plan_input") %}
{% set id = mapper[state] if state in mapper %}
{{ id }}
icon: mdi:map-marker-distance
# ========================================
# RANK & PLAN SLIDERS
# ========================================
input_number:
rank_slider:
name: "Rank limit"
min: 0
max: 24
step: 1
unit_of_measurement: h
icon: mdi:cash-clock
price_plan_slider:
name: "price limit"
min: 0
max: 400
step: 0.1
unit_of_measurement: ¢/kWh
icon: mdi:cash-lock
mode: box
input_select:
plan_input:
name: control plan
options:
- rank
- price
- price or rank
- price and rank
Itsellä ei kovin kummoisia dashboard kilkkeitä ole tehtynä, mutta käytännössä vain current & next period näkymät.
Joista laitan esiin vain 1h listat


Dashboard listan esittämiseen pitää hakea ja asentaa HACSista flex-table-card
YAML:
type: custom:flex-table-card
title: 1h period
entities:
include:
- sensor.nordpool_1h_ranked_18_17
columns:
- name: ""
data: current_period
modify: x.time.substring(11, 16)
icon: mdi:clock
- name: ""
data: current_period
modify: >-
if ( parseFloat(x.price) >= 8.0) '<div
style="background-color:firebrick;">' + x.price + ' snt</div>';
else if (x.price >= 5.0) '<div
style="background-color:gold;color:black;">' + x.price +
' snt</div>'; else if ( x.price <5.0) '<div
style="background-color:darkgreen;">' + x.price +' snt</div>';
icon: mdi:currency-eur
align: center
- name: ""
data: current_period
modify: x.rank
icon: mdi:chart-areaspline
align: right
Next period näkymän saa yo.koodilla kun vaihtaa current_period -> next_period_preview
sama taulukkokoodi toimii kaikkiin 15min, 30min ja 1h versioihin vain sensorin (sensor.nordpool_1h_ranked_18_17) nimeä muuttamalla
15min sensor.nordpool_15min_ranked_18_1745
30min sensor.nordpool_30min_ranked_18_1730