|
## |
|
# Automatically limit inverters based on grid excess with Home Assistant and OpenDTU |
|
# by Mathieu Carbou |
|
# Ref: https://gist.github.com/mathieucarbou/382556f1279d612962e03232544692d1 |
|
## |
|
|
|
# https://www.home-assistant.io/integrations/input_number |
|
input_number: |
|
# Inverters nominal power, usually 400W, 500W, 1000W, etc |
|
inverters_nominal_power: |
|
name: Inverters Nominal Power |
|
unit_of_measurement: W |
|
min: 0 |
|
max: 2000 |
|
step: 1 |
|
mode: box |
|
# Target excess or import to stay close to |
|
inverters_excess_setpoint: |
|
name: Inverters Excess Setpoint |
|
unit_of_measurement: W |
|
min: -10000 |
|
max: 10000 |
|
step: 1 |
|
mode: box |
|
# Limit sent to inverters |
|
inverters_power_limit: |
|
name: Inverters Power Limit |
|
unit_of_measurement: W |
|
min: 0 |
|
max: 2000 |
|
step: 1 |
|
mode: box |
|
|
|
# https://www.home-assistant.io/integrations/rest |
|
rest: |
|
# Get the 3ERL trend (https://3erl.fr) and limit status and expose them in sensors |
|
- resource: https://3erl.fr/api.json |
|
scan_interval: 60 |
|
binary_sensor: |
|
- name: "3ERL: Bridage Demandé" |
|
icon: "mdi:car-speed-limiter" |
|
unique_id: 347A6F4D-6B09-4A20-9C2C-D611ED23EF4B |
|
value_template: "{{ value_json['Bridage'] }}" |
|
sensor: |
|
- name: "3ERL: Tendance du jour" |
|
icon: "mdi:trending-up" |
|
unique_id: 31F1BED4-8100-44A4-88BA-DB45450611CF |
|
# multiply by 10 because the value is in c€/kWh |
|
value_template: | |
|
{% set data = value_json['PREP_Profile'] %} |
|
{% if data == '3+' %} 🟢🟢🟢 |
|
{% elif data == '2+' %} 🟢🟢 |
|
{% elif data == '1+' %} 🟢 |
|
{% elif data == '0' %} ⚠️ |
|
{% elif data == '1-' %} ⛔️ |
|
{% elif data == '2-' %} ⛔️⛔️ |
|
{% elif data == '3-' %} ⛔️⛔️⛔️ |
|
{% else %} ⁉️ |
|
{% endif %} |
|
|
|
# Get the electricity market price trend from RTE for the PRE graph |
|
- resource_template: https://www.services-rte.com/cms/open_data/v1/price/table?startDate={{ now().strftime('%d%%2F%m%%2F%Y') }} |
|
scan_interval: 60 |
|
sensor: |
|
- name: "Électricité: Prix de règlement des écarts positifs" |
|
icon: "mdi:currency-eur" |
|
unique_id: EA2D2A8C-5327-47C8-92EF-C16E9BE7A4C5 |
|
state_class: measurement |
|
unit_of_measurement: "€/MWh" |
|
value_template: "{{ value_json['values'][0]['pre']['positive'] | float }}" |
|
- name: "Électricité: Prix de règlement des écarts négatifs" |
|
icon: "mdi:currency-eur" |
|
unique_id: 95AE2165-7E9A-40BA-A555-1C7B7B93B26D |
|
value_template: "{{ value_json['values'][0]['pre']['negative'] | float }}" |
|
state_class: measurement |
|
unit_of_measurement: "€/MWh" |
|
|
|
template: |
|
# https://www.home-assistant.io/integrations/sensor/ |
|
- sensor: |
|
# Compute the number of inverters currently producing power |
|
# !! TODO !! Put your inverter S/N in this list in lowercase, any order |
|
- name: Inverters Producing Count |
|
unique_id: 7FAAECB4-2105-4F86-B63E-A1962F7FC81E |
|
state: >- |
|
{{ ['1410a01ec916', '1410a01ed6ca', '1410a01ed5a9', '1410a01ed5f0', '1410a01f092d', '1410a01ed604', '1410a01f010c', '1410a01e2cdd']|map('regex_replace', '(.+)', "sensor.inverter_\\1_power")|map('states')|map('float', 0)|select('gt', 0)|list|count }} |
|
|
|
# Total Solar Production Power |
|
# !! TODO !! Make sure you have a sensor called Solar Production Power which reflects your total plant production power. |
|
# In my case, I have 2 production groups, so I create a sensor which summarizes both here. |
|
- name: Solar Production Power |
|
unique_id: "01e37eed-3045-4eee-a786-1249567fe300" |
|
state_class: measurement |
|
device_class: power |
|
unit_of_measurement: W |
|
state: "{{ states.sensor.solar_plant_group_a_power.state|float(0) + states.sensor.solar_plant_group_b_power.state|float(0) }}" |
|
|
|
# Home Consumed Power: power consumed by your house == the produced power plus the measured grid power |
|
# !! TODO !!configure your sensor measuring your grid power here. Mine is a Shelly and is called: sensor.grid_power |
|
- name: Home Consumed Power |
|
unique_id: BE34D1AD-AB8E-4909-ACE0-BBA7D3877105 |
|
state_class: measurement |
|
device_class: power |
|
unit_of_measurement: W |
|
availability: "{{ ['sensor.solar_production_power', 'sensor.grid_power']|map('states')|map('is_number')|min }}" |
|
state: "{{ states.sensor.solar_production_power.state|float + states.sensor.grid_power.state|float }}" |
|
|
|
# Grid Returned Power: this is the gros power returned to the grid, or 0 if no excess |
|
# !! TODO !! configure your sensor measuring your grid power here. Mine is a Shelly and is called: sensor.grid_power |
|
- name: Grid Returned Power |
|
unique_id: "01e37eed-3045-4eee-a786-1249567fe214" |
|
state_class: measurement |
|
device_class: power |
|
unit_of_measurement: W |
|
availability: "{{ ['sensor.grid_power']|map('states')|map('is_number')|min }}" |
|
state: "{{ 0 - [states.sensor.grid_power.state|float, 0] | min }}" |
|
|
|
# Solar Consumed Power: This is the produced power consumed by your house |
|
- name: Solar Consumed Power |
|
unique_id: "01e37eed-3045-4eee-a786-1249567fe303" |
|
state_class: measurement |
|
device_class: power |
|
unit_of_measurement: W |
|
availability: "{{ ['sensor.solar_production_power', 'sensor.grid_returned_power']|map('states')|map('is_number')|min }}" |
|
state: "{{ [states.sensor.solar_production_power.state|float - states.sensor.grid_returned_power.state|float, 0] | max }}" |
|
|
|
# This sensor tries to calculate the daily consumed energy by your house, which is the produced energy + grid imported energy |
|
# c == this sensor last value (consumed solar production) |
|
# p == daily solar production |
|
# e == daily return to grid (loss) |
|
# we should always have: p >= c and p >= e |
|
# At midnight, yield day resets but not at the same time so we can have p < c or p < e |
|
# When not at midnight, we compare last value of c with newly computed value (c == p - e) |
|
- name: Solar Consumed Energy Daily |
|
unique_id: "01e37eed-3045-4eee-a786-1249567fe305" |
|
state_class: total_increasing |
|
device_class: energy |
|
unit_of_measurement: kWh |
|
state: >- |
|
{% set p = states.sensor.inverters_energy_meter_daily.state|float(0) %} |
|
{% set c = states.sensor.solar_consumed_energy_daily.state|float(0) %} |
|
{% set e = states.sensor.grid_energy_returned_meter_daily.state|float(0) %} |
|
{{ [0 if p == 0 or p < c or p < e else c, [0, p - e] | max] | max }} |
|
|
|
# This sensor computes your autoconumption ratio |
|
# This data is measleading through when you are registered to 3ERL, because sometimes you need to reach a 100% ratio, and sometimes you do not have to, in order to make money. |
|
- name: Solar Autoconsumption Level Daily |
|
unique_id: "01e37eed-3045-4eee-a786-1249567fe304" |
|
state_class: measurement |
|
unit_of_measurement: "%" |
|
availability: "{{ states('sensor.inverters_energy_meter_daily')|float(0) > 0 }}" |
|
state: "{{ [100, (100 - 100 * states('sensor.grid_energy_returned_meter_daily')|float(0) / states('sensor.inverters_energy_meter_daily')|float )] | min }}" |
|
|
|
sensor: |
|
# https://www.home-assistant.io/integrations/integration/ |
|
# Solar Production Energy: accumulated energy of your solar plant |
|
# This could be your Shelly if you have a simple setup, but since I have several groups, I am computing the energy based on my total produced power |
|
- platform: integration |
|
name: Solar Production Energy |
|
unique_id: E85A7E482-7937-4ABC-A0C6-1B9790DF417F |
|
source: sensor.solar_production_power |
|
unit_prefix: k |
|
# Home Consumed Energy |
|
- platform: integration |
|
name: Home Consumed Energy |
|
unique_id: E2BA4424-8CA8-4D74-9A2F-F0E5EFF84622 |
|
source: sensor.home_consumed_power |
|
unit_prefix: k |
|
|
|
# https://www.home-assistant.io/integrations/utility_meter/ |
|
utility_meter: |
|
# Grid Energy Returned Meter Daily: Daily meter for the grid returned energy. |
|
# Mine comes from a Linky key which is far more reliable than a Shelly since reads the counters directly stored in the Linky |
|
# !! TODO !! set your grid returned energy sensor here |
|
grid_energy_returned_meter_daily: |
|
name: Grid Energy Returned Meter Daily |
|
unique_id: 454B7731-375D-4C90-AFC0-42EAB23DF11D |
|
source: sensor.linky_energie_injectee |
|
always_available: true |
|
cycle: daily |
|
# Inverters Energy meter |
|
# !! TODO !! set your OpenDTU YieldTotal sensor |
|
inverters_energy: |
|
name: Inverters Energy |
|
unique_id: 6E14EC86-0965-463E-BFBF-6008B61B56AF |
|
source: sensor.opendtu_ff4930_yield_total |
|
always_available: true |
|
# Inverters Energy Meter Daily |
|
# !! TODO !! set your OpenDTU YieldTotal sensor |
|
inverters_energy_meter_daily: |
|
name: Inverters Energy Meter Daily |
|
unique_id: 4C4D8D06-C9D2-4408-B21A-1274A6E0F041 |
|
source: sensor.opendtu_ff4930_yield_total |
|
always_available: true |
|
cycle: daily |
|
# Home Consumed Energy Meter Daily: you daily home consumption |
|
home_consumed_energy_meter_daily: |
|
name: Home Consumed Energy Meter Daily |
|
unique_id: 41AAA458-7545-462F-8E78-FD1D037A8DBE |
|
source: sensor.home_consumed_energy |
|
always_available: true |
|
cycle: daily |
|
|
|
# https://www.home-assistant.io/docs/automation/ |
|
automation: |
|
# Automatically propagate changes in power limit input number to inverters |
|
# !! TODO !! set your minimal production limit (minimalPowerLimit): this should be such that minimalPowerLimit * number of inverters == minimal home consumption |
|
- id: "0000000000038" |
|
alias: "Solar: Update Inverter Power Limit" |
|
trigger: |
|
- trigger: state |
|
entity_id: |
|
- input_number.inverters_power_limit |
|
condition: [] |
|
action: |
|
- action: number.set_value |
|
data: |
|
value: >- |
|
{% set minimalPowerLimit = 100 %} |
|
{% set nominalPower = states('input_number.inverters_nominal_power')|int(minimalPowerLimit) %} |
|
{% set powerLimit = states('input_number.inverters_power_limit')|float(nominalPower) %} |
|
{{ powerLimit / nominalPower * 100.0 }} |
|
target: |
|
entity_id: |
|
- number.inverter_1410a01e2cdd_limit_nonpersistent_relative |
|
- number.inverter_1410a01ec916_limit_nonpersistent_relative |
|
- number.inverter_1410a01ed6ca_limit_nonpersistent_relative |
|
- number.inverter_1410a01ed5a9_limit_nonpersistent_relative |
|
- number.inverter_1410a01ed5f0_limit_nonpersistent_relative |
|
- number.inverter_1410a01f092d_limit_nonpersistent_relative |
|
- number.inverter_1410a01ed604_limit_nonpersistent_relative |
|
- number.inverter_1410a01f010c_limit_nonpersistent_relative |
|
|
|
# Runs at a frequent interval to update the inverters limit |
|
# !! TODO !! set your Grid Power sensor for `sensor.grid_power` |
|
# !! TODO !! update the list of entity IDs matching your inverters S/N: these sensors come from OpenDTU |
|
# !! TODO !! set your minimal production limit (minimalPowerLimit): this should be such that minimalPowerLimit * number of inverters == minimal home consumption |
|
- id: "0000000000039" |
|
alias: "Solar: Auto update power limits" |
|
trigger: |
|
- trigger: time_pattern |
|
minutes: /1 |
|
condition: |
|
- condition: state |
|
entity_id: binary_sensor.opendtu_ff4930_status |
|
state: "on" |
|
- condition: numeric_state |
|
entity_id: sensor.inverters_producing_count |
|
above: 0 |
|
action: |
|
- action: input_number.set_value |
|
data: |
|
value: >- |
|
{% set minimalPowerLimit = 100 %} |
|
{% set grid = states('sensor.grid_power')|float(0) %} |
|
{% set setpoint = states('input_number.inverters_excess_setpoint')|int(0) %} |
|
{% set nominalPower = states('input_number.inverters_nominal_power')|int(minimalPowerLimit) %} |
|
{% set powerLimit = states('input_number.inverters_power_limit')|float(nominalPower) %} |
|
{% set producing = states('sensor.inverters_producing_count')|int(0) %} |
|
{% set missedPower = (grid - setpoint) / producing if producing > 0 else nominalPower %} |
|
{{ [nominalPower, [minimalPowerLimit, powerLimit + missedPower|round(0, "ceil")]|max]|min }} |
|
target: |
|
entity_id: |
|
- input_number.inverters_power_limit |
|
|
|
# Update the setpoint depending on events |
|
# !! TODO !! set your events accordingly. For example, `binary_sensor.openevse_vehicle_connected` and `sensor.openevse_vehicle_battery_level` and `binary_sensor.openevse_vehicle_charge` are for my EV car charger. You might not need them. |
|
# !! TODO !! update the conditions: here, I need a special condition that sets the setpoint to -600 when my EV si connected so that it has enough excess to start charging. If you do not have an EV, then put -200 or -100 or 0. |
|
# The value of -5500 is because with single phase in France you cannot divert to the grid more than 6kVA. So this is a safety value in case your home is consuming less, the script will reduce the soalr production ot make sure it does not feed teh grid with more than 5.5kW. |
|
- id: "0000000000041" |
|
alias: "Solar: Auto update Setpoint" |
|
triggers: |
|
- trigger: state |
|
entity_id: |
|
- binary_sensor.3erl_bridage_demande |
|
- binary_sensor.openevse_vehicle_connected |
|
- sensor.openevse_vehicle_battery_level |
|
- binary_sensor.openevse_vehicle_charge |
|
- trigger: homeassistant |
|
event: start |
|
conditions: [] |
|
actions: |
|
- if: |
|
- condition: state |
|
entity_id: binary_sensor.3erl_bridage_demande |
|
state: "on" |
|
then: |
|
- if: |
|
- condition: state |
|
entity_id: binary_sensor.openevse_vehicle_connected |
|
state: "on" |
|
- condition: numeric_state |
|
entity_id: sensor.openevse_vehicle_battery_level |
|
below: 100 |
|
then: |
|
- action: input_number.set_value |
|
metadata: {} |
|
data: |
|
value: -600 |
|
target: |
|
entity_id: input_number.inverters_excess_setpoint |
|
else: |
|
- action: input_number.set_value |
|
metadata: {} |
|
data: |
|
value: -200 |
|
target: |
|
entity_id: input_number.inverters_excess_setpoint |
|
- if: |
|
- condition: state |
|
entity_id: binary_sensor.3erl_bridage_demande |
|
state: "off" |
|
then: |
|
- action: input_number.set_value |
|
metadata: {} |
|
data: |
|
value: -5500 |
|
target: |
|
entity_id: input_number.inverters_excess_setpoint |
|
|
|
# Notification in case a high grid volatge is detected |
|
# This is optional and require you to know how to setup HA notifications. |
|
- id: "0000000000042" |
|
alias: "Solar: Notify of high grid voltage" |
|
trigger: |
|
- trigger: numeric_state |
|
entity_id: |
|
- sensor.grid_voltage |
|
above: 250 |
|
action: |
|
- action: notify.whatsapp_mathieu |
|
data: |
|
message: "[GRID] High Voltage (> 250V)" |
|
- delay: |
|
hours: 0 |
|
minutes: 1 |
|
seconds: 0 |
|
milliseconds: 0 |
|
mode: single |
|
max_exceeded: silent |
|
|
|
# Notification in case the number of inverters producing changes. |
|
# This helps detect inverters which are dropping because if grid high voltage for example. |
|
# This is optional and require you to know how to setup HA notifications. |
|
- id: "0000000000043" |
|
alias: "Solar: Notify of inverter producing count" |
|
trigger: |
|
- trigger: state |
|
entity_id: |
|
- sensor.inverters_producing_count |
|
to: null |
|
action: |
|
- action: notify.whatsapp_mathieu |
|
data: |
|
message: "[SOLAR] Inverters producing: {{ states('sensor.inverters_producing_count')|int(0) }}" |
|
mode: single |
|
max_exceeded: silent |
|
|
|
# Notification in case 3ERL changes its zero-inject order |
|
# This is optional and require you to know how to setup HA notifications. |
|
- id: "0000000000040" |
|
alias: "Solar: Notify 3RL State Change" |
|
triggers: |
|
- trigger: state |
|
entity_id: |
|
- binary_sensor.3erl_bridage_demande |
|
to: "on" |
|
- trigger: state |
|
entity_id: |
|
- binary_sensor.3erl_bridage_demande |
|
from: "on" |
|
to: "off" |
|
- trigger: homeassistant |
|
event: start |
|
conditions: [] |
|
actions: |
|
- if: |
|
- condition: state |
|
entity_id: binary_sensor.3erl_bridage_demande |
|
state: "on" |
|
then: |
|
- action: notify.whatsapp_mathieu |
|
data: |
|
message: "[SOLAR] 3RL Bridage: ON" |
|
- if: |
|
- condition: state |
|
entity_id: binary_sensor.3erl_bridage_demande |
|
state: "off" |
|
then: |
|
- action: notify.whatsapp_mathieu |
|
data: |
|
message: "[SOLAR] 3RL Bridage: OFF" |
Hello,
thanks for the job done! It is exactly what I need as to day I inject too much power on the grid for free and looked up to 3ERL but was wondering how to block my inverters to avoid injecting on negative hours...
The issue is that I have Deye micro inverters so I can't use openDTU but I would be able still to integrate them with Solarman integration.
And I have Anker sensor as well integrated on HA not Shelly.
Do you think it would be complicated to use your integration and adapt?
I am not so familiar with HA but can work on it...
thanks in advance