> ## Content Index
> Fetch the complete content index at: https://blog.coopnet.us/llms.txt
> Use this file to discover other available public pages before exploring further.

# A solar gateway firmware update booked 11 MWh of phantom solar
- URL: https://blog.coopnet.us/solar-phantom-readings-firmware/
- Published: 2026-10-30T12:09:54.000Z
- Updated: 2026-10-31T16:52:25.000Z
- Description: An Envoy firmware update froze production and rebased the lifetime register, and Home Assistant booked about 11,562 kWh of fake solar in one hour. The CT counter never moved, so I reloaded the integration and rebuilt the statistics from it. An alert now watches the two registers.
- Author: Renstur
- Tags: Home energy, solar, home-assistant, field-notes

My Enphase Envoy updated its own firmware one morning in late September 2026, from 8.3.5289 to 8.3.6086\. I found out because Home Assistant showed 2.8 kW of solar at night, and the Energy tab said the house had used an absurd amount of power that day.

Nothing in the house had changed. The array was fine too, and one comparison proved it.

It was also the second time this year the same counter had gone bad. The first was in May.

## Two faults from one update

The production endpoint froze. Current production sat at exactly 2.827 kW for almost 23 hours, and production today read 0.0.

The lifetime production register also rebased downward, from about 25.4 MWh to about 11.6 MWh. That sensor has a state class of `total_increasing`, and Home Assistant reads a drop in one of those as a meter reset. So it counted the new value as fresh energy and booked roughly 11,562 kWh of solar into a single hour.

## Why it looked like a consumption problem

My Energy dashboard has no home consumption source configured. Home Assistant works usage out from the other flows:

```text
usage = solar + grid import - grid export + battery out - battery in

```

Any phantom in solar lands in the usage bar one for one. The consumption side looked broken, but every bit of the error came from production.

## The register that didn't move

The Envoy reports production twice. There's the lifetime register, and there's a separate energy delivered counter from the production CT. The CT counter never rebased. It read 51.9 kWh on the day of the update and 52.2 the next. Two ordinary days.

Two independent registers measure the same array, and only one jumped. That told me the panels were fine and the gateway's bookkeeping wasn't. It also gave me a clean reference to rebuild the statistics from.

My Envoy has no consumption CT, so I can't cross check the consumption side the same way.

## The repair, in order

Reloading the integration's config entry unfroze the endpoint. The lifetime register came back at 25.53 MWh, matching the CT exactly.

Then the statistics. In the statistics tool under Developer tools, I adjusted the sum on the phantom hour by minus 11,559.670 kWh. I backfilled the 23 frozen hours from the CT and checked that against the raw register movement. They agreed to 0.005 kWh.

The reload made a second phantom. Climbing from 11.6 back to 25.5 MWh looked like new production, about 13,970 kWh in the reload hour. You can't adjust that hour until its statistics row exists, which on my install was two to five minutes past the hour. So there's a short wait.

To check the repair, I compared daily solar against the CT for every day in the window. The worst day was off by 0.01 kWh. Derived usage sat 1.3 to 2.8 kWh below the Envoy's own consumption meter, but that gap shows up on clean days too. It's battery round trip loss.

## A guard for next time

Firmware shows on the Envoy's device page, but there's no entity for it, and the update itself was one warning line in the log. Nothing to trigger on directly. I watch the gap between the two registers instead. Both report MWh on my Envoy, hence the times 1000:

```yaml
template:
  - sensor:
      - name: "Solar production register divergence"
        unit_of_measurement: "kWh"
        state_class: measurement
        state: >
          {{ ((states('sensor.solar_lifetime_production') | float(0))
             - (states('sensor.solar_ct_delivered') | float(0))) | abs * 1000 }}

```

The measured noise floor was 0.012 kWh over 119 clean hours. A 1 kWh threshold for 10 minutes sits far above that and far below a fault like this one.

Divergence alone has a hole. If both registers freeze together, the gap stays at zero. So the automation has a second trigger for the lifetime register going stale:

```yaml
triggers:
  - trigger: numeric_state
    entity_id: sensor.solar_production_register_divergence
    above: 1
    for: "00:10:00"
  - trigger: template
    value_template: >
      {{ is_state('sun.sun', 'above_horizon')
         and (now() - states.sun.sun.last_changed).total_seconds() > 10800
         and (now() - states.sensor.solar_lifetime_production.last_updated).total_seconds() > 7200 }}
actions:
  - action: notify.my_phone
    data:
      message: "Solar registers disagree or stopped updating (or the array is snow covered)."

```

The sun checks matter. Home Assistant only moves `last_updated` when the state changes, so at night every Envoy sensor looks stale.

My first version only checked `above_horizon`. It fired falsely about ten minutes after sunrise on 8 of 10 mornings. The two hour clock had started at the previous evening's last reading, so the register was already stale when the sun came up, and my array doesn't produce until well after the flat horizon sunrise. Requiring the sun to be up for three hours fixed it. A restart resets `last_changed` on `sun.sun`, which only delays the alert.

## What I know and what I don't

Measured: the firmware change in the log, the frozen endpoint, the rebase, the CT staying put, and the repaired totals matching the CT day by day.

Not known: why the firmware rebased that register. That happened on the Enphase side, and I can't see into it.

## What I'd tell myself

Before touching a single statistic, compare the two production registers. The one that didn't move is both the proof and the repair source.