But i decided to start over new on my approach to charge from grid. Here is my new solution:
Needed Entities:
1.) a template-binary_sensor to display if there is more solar-power coming in then my house needs. It is TRUE if there is NOT enough solar-power to feed energy into my battery.
{% set house_power = states("sensor.senec_house_power") | int(default=0) %}
{% set solar_power = states("sensor.senec_solar_generated_power") | int(default=0) %}
{{solar_power < house_power}}
2.) a template-binary_sensor to display if my battery is full or not. It is TRUE if my battery is NOT fully charged. This is based on an battery-state-sensor from my home-battery-integration.
{% set battery_state = states("sensor.senec_system_state") %}
{{battery_state != "AKKU VOLL"}}
3.) a template-binary_sensor to display if there is not much solar-energy expected for the rest of the day. It is TRUE if there ist NOT much energy expected. It is based on an input_number and the sensor for the expected energy from the Forecast.Solar-Integration
{% set solar_energy_expected = states("sensor.solar_restproduktion_heute") | float(default=0) %}
{% set charge_from_grid_below_expected_energy = states("input_number.laden_bei_restproduktion_unter") | float(default=0) %}
{{solar_energy_expected < charge_from_grid_below_expected_energy}}
4.) a trigger-binary_sensor that catches the prices for a specific amount of hours and decides if and when there is a good time to charge from grid. The decission is based on:
- the price-difference between the cheapest and the most expensive price in the time-span
- if the price-difference is above a specific value (input_number → for me 0.1 = 10 Cents)
template:
- trigger:
- platform: time_pattern
hours: "/1"
- platform: homeassistant
event: start
action:
- service: tibber.get_prices
data:
start: "{{ (now()).strftime('%Y-%m-%d %H:') + '00:00' }}"
end: "{{ (now() + timedelta(hours=states('input_number.tibber_cheap_energy_prices_hours') | int(default=24))).strftime('%Y-%m-%d %H:%M:%S') }}"
response_variable: action_response
binary_sensor:
- unique_id: tibber_next_good_grid_charging_time
name: tibber_next_good_grid_charging_time
# replace "Zuhause" with the name of your "home" in the tibber-integration
state: >
{% set min_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | min | float(default=0) %}
{% set max_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | max | float(default=0) %}
{% set lowest_price_entry = action_response["prices"]["Zuhause"] | min(attribute='price') %}
{% set start_time_lowest_price = lowest_price_entry["start_time"] %}
{% set end_time_lowest_price = as_datetime(start_time_lowest_price) + timedelta(hours=1) %}
{{as_datetime(now()) > as_datetime(start_time_lowest_price) and as_datetime(now()) < as_datetime(end_time_lowest_price) and (max_price - min_price) > states('input_number.tibber_cheap_energy_min_price_difference') | float(default=0) }}
attributes:
min_price: >
{% set min_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | min | float(default=0) %}
{{min_price}}
max_price: >
{% set max_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | max | float(default=0) %}
{{max_price}}
price_range: >
{% set min_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | min | float(default=0) %}
{% set max_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | max | float(default=0) %}
{{max_price - min_price}}
start_time: >
{% set min_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | min | float(default=0) %}
{% set max_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | max | float(default=0) %}
{% set lowest_price_entry = action_response["prices"]["Zuhause"] | min(attribute='price') %}
{% set start_time_lowest_price = lowest_price_entry["start_time"] %}
{% set end_time_lowest_price = as_datetime(start_time_lowest_price) + timedelta(hours=1) %}
{{start_time_lowest_price}}
end_time: >
{% set min_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | min | float(default=0) %}
{% set max_price = action_response["prices"]["Zuhause"] |map(attribute='price') | list | max | float(default=0) %}
{% set lowest_price_entry = action_response["prices"]["Zuhause"] | min(attribute='price') %}
{% set start_time_lowest_price = lowest_price_entry["start_time"] %}
{% set end_time_lowest_price = as_datetime(start_time_lowest_price) + timedelta(hours=1) %}
{{end_time_lowest_price}}
prices: >
{{action_response["prices"]["Zuhause"] | list}}
That binary_sensor has a few attributes that i need for my dashboard:
- min_price => the minimum price in the fetched time-span (in €, so 0,32 means 32 Cents)
- max_price => the maximum price in the fetched time-span (in €, so 0,40 means 40 Cents)
- price_range => the difference between the min_price and the max_price (in €, so 0,08 means 8 Cents)
- start_time => the start-time of the cheapest energy-hour
- end_time => the end-time of the cheapest energy-hour
- prices => just for information: the start_times, prices and price_levels in the time-span
- a group-helper for binary_sensors that is ON when ALL of the above binary_sensors are on and OFF if at least one of the above binary_sensors is OFF. I set this up via UI like this:
It is important to toggle “all entities” on for the disrciped behavior
Charging from grid automation:
With alle the above information i made an automation the switches on the “load from grid”-switch from my home-battery-integration if the group_helper-binary_sensor is ON.
alias: Tibber - Speicher günstig laden
description: ""
triggers:
- trigger: state
entity_id:
- binary_sensor.tibber_netzladen_schalter
to: "on"
for:
hours: 0
minutes: 1
seconds: 0
id: Netzladen an
- trigger: state
entity_id:
- binary_sensor.tibber_netzladen_schalter
to: "off"
for:
hours: 0
minutes: 1
seconds: 0
id: Netzladen aus
conditions: []
actions:
- choose:
- conditions:
- condition: trigger
id:
- Netzladen an
- condition: state
entity_id: switch.senec_safe_charge
state: "off"
sequence:
- action: switch.turn_on
metadata: {}
data: {}
target:
entity_id: switch.senec_safe_charge
- conditions:
- condition: trigger
id:
- Netzladen aus
- condition: state
entity_id: switch.senec_safe_charge
state: "on"
sequence:
- action: switch.turn_off
metadata: {}
data: {}
target:
entity_id: switch.senec_safe_charge
mode: single
And for my dashboard i made an mushroom-template-card that shows my if and when the charge from grid will happen:
type: custom:mushroom-template-card
primary: >-
{% if states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'on' %}
Speicher wird aus dem Netz geladen
{% elif states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'off' and
state_attr(entity,'price_range') | float(default=0) >
states('input_number.tibber_cheap_energy_min_price_difference') %}
Speicher wird nicht aus dem Netz geladen
{% elif state_attr(entity,'price_range') | float(default=0) <
states('input_number.tibber_cheap_energy_min_price_difference') |
float(default=0) %}
Keine Netzladung in den nächsten {{states('input_number.tibber_cheap_energy_prices_hours') | int(default=0)}} Stunden
{% else %}
Geplante Speicherladung ab {{as_datetime(state_attr(entity,'start_time')).strftime('%-H:%M')}} Uhr
{% endif %}
secondary: >-
{% if states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'on' %}
Der Speicher wird gerade aus dem Netz geladen, weil der aktuelle Preis von {{(state_attr(entity,'min_price') | float(default=0) * 100) | round(1)}} Cent um {{(state_attr(entity,'price_range') | float(default=0) * 100) | round(1)}} Cent unter dem Maximalpreis liegt.
{% elif states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'off' and
state_attr(entity,'price_range') | float(default=0) >
states('input_number.tibber_cheap_energy_min_price_difference') %}
Der Speicher wird nicht aus dem Netz geladen obwohl die Konditionen günstig sind, weil Überschüsse aus der Photovoltaikanlage vorliegen oder erwartet werden.
{% elif state_attr(entity,'price_range') | float(default=0) <
states('input_number.tibber_cheap_energy_min_price_difference') |
float(default=0) %}
Die Preisdifferenz zwischen dem niedrigsten und höchsten Preis beträgt nur {{(state_attr(entity,'price_range') | float(default=0) * 100) | round(1)}} Cent. Aus dem Netz geladen wird erst ab {{(states('input_number.tibber_cheap_energy_min_price_difference') | float(default=0) * 100) | round(1)}} Cent.
{% else %}
Der Speicher wird von {{as_datetime(state_attr(entity,'start_time')).strftime('%-H:%M')}} bis {{as_datetime(state_attr(entity,'end_time')).strftime('%-H:%M')}} Uhr zu einem Preis von {{(state_attr(entity,'min_price') | float(default=0) * 100) | round(1)}} Cent aus dem Netz geladen. Die Preisdifferenz zum Maximalpreis beträgt {{(state_attr(entity,'price_range') | float(default=0) * 100) | round(1)}} Cent.
{% endif %}
icon: mdi:home-battery
entity: binary_sensor.tibber_next_good_grid_charging_time
grid_options:
columns: full
multiline_secondary: true
badge_icon: mdi:transmission-tower
badge_color: >-
{% if states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'on' %}
red
{% elif states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'off' and
state_attr(entity,'price_range') | float(default=0) >
states('input_number.tibber_cheap_energy_min_price_difference') %}
grey
{% elif state_attr(entity,'price_range') | float(default=0) <
states('input_number.tibber_cheap_energy_min_price_difference') |
float(default=0) %}
grey
{% else %}
orange
{% endif %}
icon_color: >-
{% if states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'on' %}
red
{% elif states(entity) == 'on' and
states('binary_sensor.tibber_netzladen_schalter') == 'off' and
state_attr(entity,'price_range') | float(default=0) >
states('input_number.tibber_cheap_energy_min_price_difference') %}
grey
{% elif state_attr(entity,'price_range') | float(default=0) <
states('input_number.tibber_cheap_energy_min_price_difference') |
float(default=0) %}
grey
{% else %}
orange
{% endif %}