Deye Inverter Zero Feed-in Control

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.

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

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 }}"
2 Likes

Hello JayZ,
Welcome to the Home Assistant Forum!

Thanks for contributing to the community with a new Blueprint.

I have a suggestion for you. Many people who are not familiar with directory structures will have problems installing this without the Home Assistant MY tools.

Adding a MY link for this Blueprint to your top post would help them a lot.

Here is the link to make that.
Create a link – My Home Assistant

Hello, I want to update and optimize the content of the blueprint, but I found that I can’t edit it, how should I do it, looking forward to your reply!

1 Like

Read more posts, respond to some, ask questions, like someones stuff. You level up in this Discourse platform that Home Assistant uses for the forum by using it.

I think the point is a spammer bot that creates a user for the purpose of spamming is not going to do those things or wait any time, while a legit user will automatically.