Deye Zero Feed-in Control V2

  • summary: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.

  • Remark:Testing has revealed that when the Solarman integration sets the Deye inverter’s power output percentage to 0%, the value is automatically reverted to rated power output. To resolve this behavior, the automation constrains the inverter’s minimum output power to 1% of its rated power.

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_shelly_active_power_entity:
      name: Helper entity to store last grid power (Required)
      selector:
        entity:
          domain: input_number

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
  last_power_entity_id: !input last_shelly_active_power_entity

  inv1_min_power: >
    {{ ((inv1_rated | int) * 0.01) | round(1) }}

  inv2_exists: >
    {{
      inv2_control_entity != 'none' and 
      inv2_rated | int > 0
    }}

  inv2_min_power: >
    {% if inv2_exists %}
        {{ ((inv2_rated | int) * 0.01) | round(1) }}
    {% else %}
      0
    {% endif %}

  total_min_power: >
    {{ inv1_min_power + inv2_min_power }}

  # Inverter 1 current percentage setting
  inv1_percent: >
    {{ states(inv1_control_entity) | float(0) | round(1) }}

  # Inverter 2 current percentage setting
  inv2_percent: >
    {{ (states(inv2_control_entity) | float(0) | round(1)) if inv2_exists else 0 }}

trigger:
  - platform: time_pattern
    # Timer triggered every 9 seconds
    seconds: /9

condition:
  - condition: template
    # Automation is triggered when the current purchased power is greater than 1% of the sum of the rated power of all the inverters 
    # or when the current is feeding and the micro-reverse is not the minimum (1%) output.
    value_template: >
      {% set value = states(shelly_entity) | float(0) %}
      {{ (value < 0 and (inv1_percent > 1 or inv2_percent > 1)) or value > total_min_power }}

actions:
  - variables:
      last_power: >
        {{ states(last_power_entity_id) | float(0) | round(1) }}

      # Current grid power (positive = importing, negative = exporting)
      grid_power: >
        {{ states(shelly_entity) | float(0) | round(1) }}

      # Check whether the grid power changes,
      # no change means that the power data of Shelly has not been updated, 、
      # and the command of the inverter output power has not been issued successfully, try to retry.
      power_unchanged: >
        {{ (grid_power - last_power) | abs < 1 }}

      # Inverter 1 current output power = (percentage * rated power) / 100
      inv1_current_power: >
        {{ (inv1_percent * (inv1_rated | int)) / 100 | round(1) }}

      # 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 }}

      # Total rated power
      total_rated: >
        {{ (inv1_rated | int) + (inv2_rated | int if inv2_exists else 0) }}

      total_target: >
        {{ (total_current | round(1)) if power_unchanged else ((total_current + grid_power) | round(1))}}

      # Allocation percentage
      allocation_percent: >
        {{
          (total_target * 100) / total_rated | round(1) if total_rated > 0 else 0
        }}

      # Inverter 1 target power with 1% minimum
      inv1_target_power: >
        {% set unclamped = (inv1_rated | int * allocation_percent) / 100 %}
        {% set min_power = (inv1_rated | int) * 0.01 %}
        {% if unclamped < min_power%}
          {{ min_power | round(1) }}
        {% 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 with 1% minimum
      inv2_target_power: >
        {% if inv2_exists %}
          {% set unclamped = (inv2_rated | int * allocation_percent) / 100 %}
          {% set min_power = (inv2_rated | int) * 0.01 %}
          {% if unclamped < min_power %}
            {{ min_power | round(1) }}
          {% 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 ~ "W (Inv1:" ~ inv1_current_power ~ "W@" ~ inv1_percent ~ "%)" ~ " | " ~
          "power_unchanged:" ~ power_unchanged ~
          (", Inv2:" ~ inv2_current_power ~ "W@" ~ inv2_percent ~ "%" if inv2_exists else "") ~ " | " ~
          "inv1_rated:" ~ inv1_rated ~ "W | " ~
          ("inv2_rated:" ~ inv2_rated ~ "W | " if inv2_exists else "") ~ " | " ~
          "Target:" ~ total_target ~ "W | " ~
          "inv1_target_power:" ~ inv1_target_power ~ "W" ~
          (" | inv2_target_power:" ~ inv2_target_power ~ "W" if inv2_exists else "") ~
          " | Set: Inv1=" ~ inv1_target_percent ~ "%" ~
          (", Inv2=" ~ inv2_target_percent ~ "%" if inv2_exists else "")
        }}

  # 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 }}"

  - service: input_number.set_value
    target:
      entity_id: "{{ last_power_entity_id }}"
    data:
      value: "{{ grid_power | float }}"

Hello,

Here’s your import link (host your YAML at that URL):

Replace YOUR_USERNAME and YOUR_GIST_ID with your GitHub Gist details. Want me mymilestonecard com to create the Gist for you?

Best Regard,
Garima