blueprint: author: marc-romu domain: automation homeassistant: min_version: 2024.6.0 name: 'PVPC Optimizer v2: Turn on a device based on the hourly electricity prices' description: > This blueprint is ideal for all those who want to automate based on the PVPC electricity pricing in Spain. The process is simple: 1. Select the [PVPC sensor](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/) (you have to set it up first in the configuration). 2. Choose the device you wish to control. 3. Specify how many hours you want it to run. 4. Specify whether you want to run your device during the cheapest or most expensive periods. 5. Optionally, specify the number of hours to wait before turning the device on again. Set to 0 to omit this setting. 6. Optionally, select some hours you want to exclude from the optimization. This blueprint will automatically turn on the device for the specified number of hours on the cheapest/most expensive periods, and turn it back off for the rest of the day. Note: If the device you want to run cannot be passed to the `homeassistant.turn_on` service, you can create a script and call this script from the blueprint. input: pvpc_sensor: name: PVPC sensor description: 'REQUIRED. Sensor created using the PVPC integration.' selector: entity: hours: name: How many hours? description: 'REQUIRED. Number of hours per day you want the device to be on.' selector: number: min: 1 max: 24 step: 1 unit_of_measurement: "h" mode: slider tariff: name: When to turn on the device? description: 'Choose whether you want to turn on your device during the cheapest or most expensive periods of the day. By default, devices will be turned on during the cheapest prices.' default: During cheapest prices selector: select: options: - During cheapest prices - During most expensive prices multiple: false device: name: Devices to control icon: mdi:target collapsed: false input: target_device: name: Target devices description: 'Device to control. This parameter is optional. If you do not specify any device here, you can also trigger actions below.' default: {} selector: target: turn_on_boolean: name: Turn on the devices? description: Do you want to turn on the devices when the pricing conditions are met? default: true selector: boolean: turn_off_boolean: name: Turn off the devices? description: Do you want to turn off the devices when the pricing conditions are not met? default: true selector: boolean: additional: name: Additional parameters collapsed: true icon: mdi:help input: time_gap: name: Time gap description: 'Optional: Number of hours to wait before turning the device on again. IMPORTANT: This works only within the same day. For example, if the minimum time gap is set to 4h, and the device is turned on at 22:00, after 00:00 it can be turned on again without waiting 4h.' default: 0 selector: number: min: 0 max: 23 step: 1 unit_of_measurement: "h" mode: slider exclude_hours: name: Exclude some hours description: 'Optionally, select hours you want to exclude from the optimization. The price for the selected hours will be totally omitted, and the cheapest/most expensive prices will be calculated just from the remaining hours.' default: [] selector: select: options: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'] multiple: true actions_yes: name: Action on selected hours description: Action to execute when the pricing conditions are met. icon: mdi:check collapsed: true input: action_on_before: name: Actions before turning on the device default: [] selector: action: {} action_on_after: name: Actions after turning on the device default: [] selector: action: {} actions_no: name: Action for the rest of the day description: Action to execute when the pricing conditions are NOT met. icon: mdi:close collapsed: true input: action_off_before: name: Actions before turning off the device default: [] selector: action: {} action_off_after: name: Actions after turning off the device default: [] selector: action: {} mode: queued max: 20 max_exceeded: warning trigger: - platform: state entity_id: !input pvpc_sensor variables: pvpc_sensor: !input pvpc_sensor pvpc_position: '{{ state_attr( pvpc_sensor ,"price_position") }}' target_device: !input target_device hours: !input hours tariff: !input tariff tariff_high: '{{ tariff == "During most expensive prices" }}' time_gap: !input time_gap exclude_hours: !input exclude_hours current_hour: '{{ now().hour }}' prices: > {% set prices = namespace(data = []) %} {% for hour in range(24) %} {% set attribute_name = "price_" + "%02d" % hour + "h" %} {% set price = state_attr( pvpc_sensor , attribute_name ) %} {% set prices.data = prices.data + [price] %} {% endfor %} {{ prices.data | list }} when_to_turn_on: > {% set time_gap = time_gap|int %} {% set hours = hours|int %} {% set data = namespace( prices_list = prices|list, result = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]) %} {% set price_placeholder_used = 9999 if tariff_high == false else -9999 %} {# Exclude hours from the prices_list #} {% if exclude_hours|length > 0 %} {% for hour in exclude_hours %} {% set hour = hour|int %} {% set data.prices_list = data.prices_list[:hour] + [price_placeholder_used] + data.prices_list[hour + 1:] %} {% endfor %} {% endif %} {# Function to find the index of the maximum or minimum value within a range #} {% macro find_minmax_index(list, start, end, max=false) %} {% set index = namespace(data = [start]) %} {% for i in range(start, end) %} {% if (list[i]|float > list[index.data.0]|float and max) or (list[i]|float < list[index.data.0]|float and not max) %} {% set index.data = [i] %} {% endif %} {% endfor %} {{ index.data.0|int }} {% endmacro %} {# Loop for the specified number of hours #} {% for _ in range(hours) %} {# Find the index of the first maximum or minimum value in prices_list within the range [0, 23] #} {% set index_value = find_minmax_index(data.prices_list, 0, 24, tariff_high)|int %} {% if (tariff_high == false and data.prices_list[index_value] < price_placeholder_used) or (tariff_high == true and data.prices_list[index_value] > price_placeholder_used) %} {# Set the value at the min_index to 1 by creating a new list with the updated value #} {% set data.result = data.result[:index_value] + [1] + data.result[index_value+1:] %} {# Set the value at the prices_list to price_placeholder_used by creating a new list with the updated value #} {% set data.prices_list = data.prices_list[:index_value] + [price_placeholder_used] + data.prices_list[index_value+1:] %} {# Set the surrounding X hours to price_placeholder_used by creating a new list with the updated values, this way they won't be used again #} {% for i in range(1, time_gap + 1) %} {% set item_next = (index_value + i) %} {% set item_previous = (index_value - i) %} {% if item_next >= 0 and item_next <= 23 %} {% set data.prices_list = data.prices_list[:(item_next)] + [price_placeholder_used] + data.prices_list[(item_next) + 1:] %} {% endif %} {% if item_previous >= 0 and item_previous <= 23 %} {% set data.prices_list = data.prices_list[:(item_previous)] + [price_placeholder_used] + data.prices_list[(item_previous) + 1:] %} {% endif %} {% endfor %} {% endif %} {% endfor %} {{ data.result | list }} turn_on_boolean: !input turn_on_boolean turn_off_boolean: !input turn_off_boolean condition: action: - if: "{{ when_to_turn_on[current_hour] == 1 }}" then: - if: "{{ true }}" then: !input action_on_before - if: "{{ turn_on_boolean }}" then: - service: homeassistant.turn_on target: !input target_device - if: "{{ true }}" then: !input action_on_after else: - if: "{{ true }}" then: !input action_off_before - if: "{{ turn_off_boolean }}" then: - service: homeassistant.turn_off target: !input target_device - if: "{{ true }}" then: !input action_off_after