Loading...
Author Cloudapp
E.G.

Detect a Failing PV String in Home Assistant: Shading, Dead Module or Loose MC4

June 15, 2026
Table of Contents

A PV string can quietly underperform for months, and you only notice on the annual statement. Partial shading from a tree that grew taller, a module with a failed bypass diode, an MC4 connector that worked itself loose over the years — each one costs yield without throwing a single error. My inverter happily shows green while half a string sits in shade.

The fix is surprisingly simple and needs no extra hardware: if your system has two MPPT trackers, the two strings should track each other closely throughout the day when they face the same direction. If they drift apart for any length of time, something is wrong. Here is how to watch for exactly that in Home Assistant, in three steps.

The idea: compare your two MPPT strings

On a system with two equally-oriented strings, the current per string is nearly identical across the day. Voltage tells you little (it stays fairly stable even under shade), but current drops the moment a module in one string gets less light or is electrically worse connected. So we continuously compute the percentage difference between the two string currents and raise an alert when it stays above a threshold — not on a passing cloud, but on a sustained deviation.

Step 1 — read each string's current over Modbus

The data comes from the inverter's Modbus holding registers. On my Huawei SUN2000, voltage and current per string sit right next to each other: 32016/32017 for string 1, 32018/32019 for string 2 (voltage scale 0.1, current scale 0.01). How to wire the inverter up over Modbus in the first place is covered in the Modbus basics post.

# inside a Modbus TCP hub (host/port = your own inverter/SDongle/proxy)
sensors:
  - name: "PV String 1 Voltage"
    slave: 1
    address: 32016
    input_type: holding
    data_type: int16
    scale: 0.1
    unit_of_measurement: "V"
    device_class: voltage
    scan_interval: 60
  - name: "PV String 1 Current"
    slave: 1
    address: 32017
    input_type: holding
    data_type: int16
    scale: 0.01
    precision: 2
    unit_of_measurement: "A"
    device_class: current
    scan_interval: 60
  - name: "PV String 2 Voltage"
    slave: 1
    address: 32018
    input_type: holding
    data_type: int16
    scale: 0.1
    unit_of_measurement: "V"
    device_class: voltage
    scan_interval: 60
  - name: "PV String 2 Current"
    slave: 1
    address: 32019
    input_type: holding
    data_type: int16
    scale: 0.01
    precision: 2
    unit_of_measurement: "A"
    device_class: current
    scan_interval: 60

Register addresses are vendor-specific. On a different inverter (Fronius, SMA, GoodWe) you'll find the string registers in the vendor's Modbus map — the logic after that is identical.

Step 2 — a template sensor for the percentage difference

This template sensor computes the deviation relative to the stronger of the two strings. The key detail is the 0.5 A floor: at dawn and dusk, when both strings deliver near zero, any tiny mismatch would blow up to a huge percentage — the floor masks that twilight window and cleanly returns 0.

- name: "PV String Difference"
  unique_id: pv_string_difference
  unit_of_measurement: "%"
  icon: "mdi:solar-panel"
  state: >
    {% set s1 = states('sensor.pv_string_1_current') | float(0) %}
    {% set s2 = states('sensor.pv_string_2_current') | float(0) %}
    {% set max_val = [s1, s2] | max %}
    {% if max_val > 0.5 %}
      {{ ((s1 - s2) | abs / max_val * 100) | round(1) }}
    {% else %}
      0
    {% endif %}

Step 3 — the alert automation with a false-alarm guard

Now it all comes together. The automation fires when the difference exceeds 30 % and holds for 30 minutes. The second guard is the condition "string 1 current above 1 A": it prevents false alarms at night and around sunrise/sunset, when the array barely produces anything anyway.

- id: pv_string_anomaly_warning
  alias: "PV String Anomaly Warning"
  description: "Warn when PV strings differ by >30% for >30 min"
  triggers:
  - trigger: numeric_state
    entity_id: sensor.pv_string_difference
    above: 30
    for:
      minutes: 30
  conditions:
  - condition: numeric_state
    entity_id: sensor.pv_string_1_current
    above: 1
  actions:
  - action: notify.mobile_app_your_phone
    data:
      title: "PV String Anomaly"
      message: >-
        String 1: {{ states('sensor.pv_string_1_voltage') }}V / {{ states('sensor.pv_string_1_current') }}A |
        String 2: {{ states('sensor.pv_string_2_voltage') }}V / {{ states('sensor.pv_string_2_current') }}A |
        Difference: {{ states('sensor.pv_string_difference') }}% |
        Check for shading, a dead module or a loose connector.
  mode: single

The push message names voltage and current of both strings plus the difference — you see right on the lock screen which string is weak, without opening the app. Replace notify.mobile_app_your_phone with your own mobile device.

What the warning actually catches

In practice it's three causes. Shading: a tree, a new rooftop structure, or in winter the chimney's afternoon shadow — the affected string drops reproducibly at the same time of day. A dead module: a blown bypass diode or microcracks pull one string down permanently. A loose MC4 connector: a connector that has corroded over the years or never quite clicked in raises resistance — dangerous, because in the worst case it gets hot. A 30 % difference that doesn't depend on the time of day is exactly the signal worth chasing.

Tuning the thresholds

30 % and 30 minutes are a good starting point for two nominally identical strings. For strings of different size or orientation (east/west) a constant baseline difference is normal — then raise the threshold, or compare specific yield per module instead. Want a more sensitive warning? Drop to 20 %. Want quiet? Stretch the for duration to 60 minutes.

Frequently asked questions

Why 30 % and not a smaller threshold?

Below about 20 % you're in the range of normal scatter from drifting clouds, slightly different module temperatures and measurement noise. 30 % over 30 minutes reliably separates real faults from weather. For very uniform systems you can carefully tighten it after a few weeks of observation.

Does this work with a single-string inverter?

The direct string comparison doesn't — it needs two MPPT trackers. On a single-string system you'd compare actual against expected power (from irradiance/time of day) instead, which is considerably more work. That's exactly why the two-string comparison is so attractive: it needs no reference, the strings are each other's reference.

Will it false-alarm on cloudy days?

No — clouds hit both strings at once, so the difference stays small. That's precisely why we compare the strings against each other rather than against a fixed value. The 30-minute condition additionally absorbs short, uneven shadow passes.

Which Modbus registers does my inverter use?

The 32016–32019 above are for Huawei SUN2000. Other vendors have their own maps; search your inverter's Modbus document for "PV1 Voltage/Current" and "PV2 Voltage/Current". The scale factors (0.1 for voltage, 0.01 for current) are vendor-dependent too — if values are off by a factor of 10, it's almost always the scale.

Related articles