Automated zero feed-in control for 1-2 Deye solar inverters using real-time grid power monitoring from Shelly meter. Prevents reverse power flow to the grid while maximizing self-consumption.
blueprint:
name: Deye Zero Feed-in Control
description: Automated zero-feed-in control for one or two Deye inverters using Shelly power meter
domain: automation
input:
shelly_power_sensor:
name: Shelly total active power sensor (Required)
selector:
entity:
domain: sensor
device_class: power
inverter1_control:
name: Inverter 1 active power regulation entity (Required)
selector:
entity:
domain: number
inverter1_rated:
name: Inverter 1 rated power (W) (Required)
selector:
number:
min: 300
max: 2500
step: 100
unit_of_measurement: W
inverter2_control:
name: Inverter 2 active power regulation entity (Optional)
default: none
selector:
entity:
domain: number
inverter2_rated:
name: Inverter 2 rated power (W) (Optional)
default: 0
selector:
number:
min: 300
max: 2500
step: 100
unit_of_measurement: W
last_trigger_entity:
name: Last trigger time entity (Required)
description: Select a datetime entity to store the last trigger time
default: input_datetime.deye_last_trigger_time
selector:
entity:
domain: input_datetime
variables:
shelly_entity: !input shelly_power_sensor
inv1_control_entity: !input inverter1_control
inv1_rated: !input inverter1_rated
inv2_control_entity: !input inverter2_control
inv2_rated: !input inverter2_rated
time_entity: !input last_trigger_entity
# Get last trigger timestamp from entity state
last_trigger_timestamp: >
{% set stored_time = states(time_entity) %}
{% if stored_time not in ['unavailable', 'unknown', None] %}
{{ as_timestamp(stored_time) | int }}
{% else %}
0
{% endif %}
trigger:
- platform: state
entity_id: !input shelly_power_sensor
condition:
# 3-second interval check (Deye inverter has slow response to power adjustments; 3s interval is optimal based on validation to avoid frequent changes)
- condition: template
value_template: >
{{ (now().timestamp() | int - last_trigger_timestamp) >= 3 }}
actions:
# Calculate all values
- variables:
# Current grid power (positive = importing, negative = exporting)
grid_power: "{{ states(shelly_entity) | int(0) }}"
# Inverter 1 current percentage setting
inv1_percent: "{{ states(inv1_control_entity) | float(0) | round(1) }}"
# Inverter 1 current output power = (percentage * rated power) / 100
inv1_current_power: "{{ (inv1_percent * (inv1_rated | int)) / 100 | round(1) }}"
# Check if inverter 2 exists
inv2_exists: >
{{
inv2_control_entity != 'none' and
inv2_rated | int > 0
}}
# Inverter 2 current percentage setting
inv2_percent: >
{{ (states(inv2_control_entity) | float(0) | round(1)) if inv2_exists else 0 }}
# Inverter 2 current output power = (percentage * rated power) / 100
inv2_current_power: >
{{
(inv2_percent * (inv2_rated | int)) / 100 | round(1)
if inv2_exists
else 0
}}
# Current total output power
total_current: "{{ inv1_current_power + inv2_current_power }}"
# Target total output power = current output + grid power
total_target: "{{ total_current + grid_power }}"
# Total rated power
total_rated: "{{ (inv1_rated | int) + (inv2_rated | int if inv2_exists else 0) }}"
# Allocation percentage
allocation_percent: >
{{
(total_target * 100) / total_rated | round(1) if total_rated > 0 else 0
}}
# Inverter 1 target power and percentage
inv1_target_power_unclamped: >
{{ (inv1_rated | int * allocation_percent) / 100 | round(1) }}
inv1_target_power: >
{% set unclamped = (inv1_rated | int * allocation_percent) / 100 %}
{% if unclamped < 0 %}
0
{% elif unclamped > inv1_rated | int %}
{{ inv1_rated | int }}
{% else %}
{{ unclamped | round(1) }}
{% endif %}
inv1_target_percent: >
{% if (inv1_rated | int) > 0 %}
{{ (inv1_target_power * 100) / (inv1_rated | int) | round(1) }}
{% else %}
0
{% endif %}
# Inverter 2 target power and percentage
inv2_target_power_unclamped: >
{{ ((inv2_rated | int * allocation_percent) / 100 | round(1)) if inv2_exists else 0 }}
inv2_target_power: >
{% if inv2_exists %}
{% set unclamped = (inv2_rated | int * allocation_percent) / 100 %}
{% if unclamped < 0 %}
0
{% elif unclamped > inv2_rated | int %}
{{ inv2_rated | int }}
{% else %}
{{ unclamped | round(1) }}
{% endif %}
{% else %}
0
{% endif %}
inv2_target_percent: >
{% if inv2_exists and (inv2_rated | int) > 0 %}
{{ (inv2_target_power * 100) / (inv2_rated | int) | round(1) }}
{% else %}
0
{% endif %}
log_message: >
{{
"Grid:" ~ grid_power ~ "W | " ~
"Current:" ~ total_current | round(1) ~ "W (Inv1:" ~ inv1_current_power | round(1) ~ "W@" ~ inv1_percent | round(1) ~ "%)" ~
(", Inv2:" ~ inv2_current_power | round(1) ~ "W@" ~ inv2_percent | round(1) ~ "%" if inv2_exists else "") ~ " | " ~
"inv1_rated:" ~ inv1_rated ~ "W | " ~
("inv2_rated:" ~ inv2_rated ~ "W | " if inv2_exists else "") ~ " | " ~
"Target:" ~ total_target | round(1) ~ "W | " ~
"inv1_target_power:" ~ inv1_target_power | round(1) ~ "W" ~
(" | inv2_target_power:" ~ inv2_target_power | round(1) ~ "W" if inv2_exists else "") ~
" | Set: Inv1=" ~ inv1_target_percent | round(1) ~ "%" ~
(", Inv2=" ~ inv2_target_percent | round(1) ~ "%" if inv2_exists else "") ~
" | TimeDiff: " ~ (now().timestamp() | int - last_trigger_timestamp) ~ "s"
}}
# Log record
- service: system_log.write
data:
level: info
message: "ZeroFid: {{ now().strftime('%H:%M:%S') }} | {{ log_message }}"
# Control inverter 1
- service: number.set_value
target:
entity_id: "{{ inv1_control_entity }}"
data:
value: "{{ inv1_target_percent | float }}"
# Control inverter 2 only if exists
- if:
- condition: template
value_template: "{{ inv2_exists }}"
then:
- service: number.set_value
target:
entity_id: "{{ inv2_control_entity }}"
data:
value: "{{ inv2_target_percent | float }}"
# Update last trigger time entity
- service: input_datetime.set_datetime
target:
entity_id: "{{ time_entity }}"
data:
timestamp: "{{ now().timestamp() | int }}"