Automating heating the house is kind of hard when considering hourly prices
Here I try explaining the concept of heating my one family house with 2 air heat pumps, central water heating system with multiple smart valves added to radiators.
All other concepts have been concentrating on heating only on when energy price is cheap, like heating x most cheap hours a day.
My approach to heating is that I want to live in pleasant environment, have minimal impact on house climate while minimizing energy cost. I do need to heat house all the time especially when temperature outdoors is -25°C.
As most, I used to cut with automation “high” electric prices of the day as it easy to find those hours. But then I started thinking other way around: if I would find the highest difference between X sequential prices and X sequential prices before that, so the sparing the money is the most. Imagine that you look up the chart
and see that on the morning before prices go high, you could warm up the house a bit more and then cut the heating. And do it automatically on every occasion on the sequence width of your choosing.
So I did come up a concept and implemented it on my system on which I could maybe spare more money while finding more those high peak/ highest prices of the day:
- find X hours sequence that is most expensive than the previous X hours sequence
- use same algorithm to find X hours sequence before and after the most expensive X hours sequence to further maximize sparing money
- heat more before the expensive hours sequence, maybe set 1-2 degrees more to heating elements
- set the heating off/ minimal on the expensive hours sequence
Concept premises:
- have a list of today prices of energy, price per hour (24h per day)
- I’m using norpool which has attribute for “today” prices on list
The basics of the system is the sensors which I use in my automations like one below (the checking if nordpool is available is not necessary, but it is good to output recent value when the cloud value is not available). The /config/configuration.yaml -file has an entry
template: !include_dir_merge_list template/
and the following code of sensors is in the “/config/template/template_sensor.yaml” -file:
- sensor:
- name: "nordpool_price_most_savings_sequential_hours_energy_today"
unique_id: nordpool_price_most_savings_sequential_hours_energy_today
state: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{states('sensor.nordpool_price_most_savings_sequential_hours_energy_today')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = 23 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_index | float (0)}}
{% endif %}
attributes:
total_diff_price: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_most_savings_sequential_hours_energy_today','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = 23 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_diff | float (0)}}
{% endif %}
enough_percentage_difference: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_most_savings_sequential_hours_energy_today','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = 23 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.enough_percentage_difference}}
{% endif %}
- name: "nordpool_price_most_savings_sequential_hours_energy_today_datetime"
unique_id: nordpool_price_most_savings_sequential_hours_energy_today_datetime
state: >
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today')|float(-1) < 0) %}
{{today_at("00:00") + timedelta( hours = (0 - 480000))}}
{% else %}
{% if (state_attr('sensor.nordpool_price_most_savings_sequential_hours_energy_today','total_diff_price')|float(-1) < 0 )%}
{{state_attr('schedule.valtakunnanverkon_energiansaasto_aikataulu_lammitys_pois_paalta','next_event')}}
{% else %}
{% if (state_attr('sensor.nordpool_price_most_savings_sequential_hours_energy_today','total_diff_price') | float (-1) > states('input_number.nordpool_price_savings_minimum_price_difference') | int (6) ) and (state_attr('sensor.nordpool_price_most_savings_sequential_hours_energy_today','enough_percentage_difference'))%}
{{ today_at("00:00") + timedelta(hours = states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | float (0))}}
{% else %}
{{ today_at('00:00') + timedelta( hours = (0 - 480000)) }}
{% endif %}
{% endif %}
{% endif %}
- name: "nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours"
unique_id: nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours
state: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) - numberOfSequentialHours) %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_index | float (0)}}
{% endif %}
attributes:
total_diff_price: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) - numberOfSequentialHours) %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_diff | float (0)}}
{% endif %}
enough_percentage_difference: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (0+numberOfSequentialHours) %}
{% set lastHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) - numberOfSequentialHours) %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.enough_percentage_difference}}
{% endif %}
- name: "nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours_datetime"
unique_id: nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours_datetime
state: >
{% if (states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours')|float(-1) < 0) %}
{{today_at("00:00") + timedelta( hours = (0 - 480000))}}
{% else %}
{% if (state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours','total_diff_price') | float (-1) > states('input_number.nordpool_price_savings_minimum_price_difference') | int (6) ) and (state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours','enough_percentage_difference'))%}
{{ today_at("00:00") + timedelta(hours = states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_before_most_savings_sequential_hours') | float (0))}}
{% else %}
{{ today_at('00:00') + timedelta( hours = (0 - 480000)) }}
{% endif %}
{% endif %}
- name: "nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours"
unique_id: nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours
state: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) + numberOfSequentialHours) %}
{% set lastHour = 24 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_index | float (0)}}
{% endif %}
attributes:
total_diff_price: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) + numberOfSequentialHours) %}
{% set lastHour = 24 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.max_diff | float (0)}}
{% endif %}
enough_percentage_difference: >
{% if (states('sensor.nordpool_kwh_fi') | lower == "unavailable" or states('sensor.nordpool_kwh_fi') | lower == "unknown" )%}
{% if (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unavailable" or states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | lower == "unknown" )%}
{{(-1) | float (-1)}}
{% else %}
{{state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours','total_diff_price')}}
{% endif %}
{% else %}
{% set numberOfSequentialHours = states('input_number.nordpool_price_savings_sequential_hours') | int (4) %}
{% set firstHour = (states('sensor.nordpool_price_most_savings_sequential_hours_energy_today') | int (4) + numberOfSequentialHours) %}
{% set lastHour = 24 %}
{% set ns = namespace(max_diff=-1000000000, max_index=-1, enough_percentage_difference=false) %}
{% for i in range(firstHour, lastHour - numberOfSequentialHours + 1) %}
{% set current_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i:i + numberOfSequentialHours] | list | sum %}
{% set previous_sum = state_attr('sensor.nordpool_kwh_fi', 'today')[i - numberOfSequentialHours:i] | list | sum %}
{% set diff = current_sum - previous_sum %}
{% if diff > ns.max_diff %}
{% set ns.max_diff = diff %}
{% set ns.max_index = i %}
{% set ns.enough_percentage_difference = (previous_sum *(states('input_number.nordpool_price_savings_minimum_price_multiplier') | float (1))/current_sum<1) %}
{% endif %}
{% endfor %}
{{ns.enough_percentage_difference}}
{% endif %}
- name: "nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours_datetime"
unique_id: nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours_datetime
state: >
{% if (states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours')|float(-1) < 0) %}
{{today_at("00:00") + timedelta( hours = (0 - 480000))}}
{% else %}
{% if (state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours','total_diff_price') | float (-1) > states('input_number.nordpool_price_savings_minimum_price_difference') | int (6) ) and (state_attr('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours','enough_percentage_difference'))%}
{{ today_at("00:00") + timedelta(hours = states('sensor.nordpool_price_2nd_most_savings_sequential_hours_energy_today_after_most_savings_sequential_hours') | float (0))}}
{% else %}
{{ today_at('00:00') + timedelta( hours = (0 - 480000)) }}
{% endif %}
{% endif %}
continuing…