Skip to content

fix(gateway): ignore inverters reported without a serial - #5118

Open
mgazza wants to merge 3 commits into
mainfrom
fix/gateway-ignore-blank-serial
Open

mgazza wants to merge 3 commits into
mainfrom
fix/gateway-ignore-blank-serial

Conversation

@mgazza

@mgazza mgazza commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

A gateway hub briefly reported a status listing one GivEnergy inverter with an empty serial and no battery block. The gateway component treated it as a real unit:

  • _needs_reconfigure() counted '' as a newly discovered serial and re-ran auto-config (new inverter(s) discovered ['']).
  • automatic_config() bound inverter 0 to that entry, so every entity arg used an empty suffix (number.predbat_gateway__reserve_soc, ...) and every control command went out with "serial": "". The writes failed their read-back (Inverter 0 write to reserve failed), SoC read 0, and soc_max fell back to its default with a soc_max element None validation error.
  • _inject_entities() published predbat_gateway__* entities for the phantom unit.

Control only recovered when the real serial reappeared, counted as "new" again and triggered another re-config.

Fix

A blank serial only means "missing" for GivEnergy units (inverter, EMS, Gateway). GivEnergy discovery always reads the serial, whereas the other hub drivers never report one, so their inverters always arrive with serial "". For every other type nothing changes: a blank serial binds exactly as before.

For a GivEnergy unit reported without its serial (_serial_missing()):

  • Re-config: _needs_reconfigure() never treats it as a new inverter.
  • Binding: automatic_config() never binds it, but it still counts when deciding the control point. Dropping it first could choose wrong: a second AIO without its serial would stop the Gateway being chosen.
  • Unidentified EMS or Gateway: while one is listed, the control point itself is uncertain, so auto-config skips that status (including a Gateway with a single AIO). It also skips when nothing addressable was chosen. In both cases the existing binding and _auto_configured are left alone, so a first-run config retries on the next status. When the serial is reported it is a new serial and re-runs auto-config.
  • Status with only such units: it isn't counted as inverter telemetry. The last good status and the telemetry freshness are kept, so a long spell shows up through the existing staleness check. The gateway-online, EV charger and EMS aggregate entities are still published, and new EV chargers are registered straight away.
  • Entities: no per-inverter or inverter_time entities, so no predbat_gateway__* entities appear. EMS aggregates carry no serial in their names and are still published.
  • EV chargers when auto-config skips: a new charger is registered as well, so it doesn't re-trigger auto-config (and its log lines) on every status.
  • Warnings: the missing-serial and auto-config-skipped warnings are logged at most once every ten minutes each.

Tests

All in TestGatewayUnitControlBinding, most driven through _process_telemetry():

  • Next to a real inverter on first telemetry: a blank serial (empty and whitespace, with and without a battery block) is not bound, remembered or published.
  • After a good config: a blank-only status keeps the last good status and telemetry time. A mixed status triggers no re-config. The binding and configured serials are unchanged, and a forced automatic_config() keeps the binding and warns.
  • Blank only on first telemetry: the gateway is not configured, the API is not started and the status is not counted as telemetry. The next real status configures normally.
  • Unidentified EMS, or Gateway with two AIOs or with one: nothing is bound, and EMS aggregates are still published. Once the serial arrives the right control point is bound. After a good config an unidentified Gateway doesn't move the binding.
  • Second AIO without a serial behind a Gateway: it still makes the Gateway the control point.
  • Blank-only status with an EV charger: the charger entities are published and the car registered, the binding and telemetry time stay put, and the warning is logged once until the interval passes.
  • Skipped auto-config with a new EV charger: the charger is registered, with one "skipped" and one "new charger" log line over repeated statuses. This holds both on first run and on a configured site.
  • Non-GivEnergy types (Solis, Deye, unknown) with serial "": these still configure as before.

On main all but the non-GivEnergy test fail (that one checks main's behaviour is kept). I also reverted each guard individually (type scoping, whitespace strip, coordinator rule, binding filter, EMS lookup, entity filter, telemetry early-out, re-config filter, configured-serials filter, EV registration in both paths, rate limit), and each revert makes at least one test fail. ./run_all --test gateway passes (284) and ./run_all --quick passes.

🤖 Generated with Claude Code

mgazza and others added 3 commits September 16, 2026 17:56
A hub can briefly report an inverter with an empty serial and no battery
block. _needs_reconfigure() counted '' as a newly discovered inverter,
automatic_config() then bound inverter 0 to it, and every control command
went out with an empty serial and failed its read-back until the real
serial reappeared and triggered another re-config. _inject_entities() also
published predbat_gateway__* entities for the phantom.

- _needs_reconfigure() never treats a serial-less inverter as new.
- automatic_config() never binds a serial-less inverter. Such units still
  count when classifying the topology, so an EMS or Gateway that has not
  reported its serial yet is not bypassed in favour of its AIOs. If nothing
  addressable is selected it logs a warning and returns without touching
  the existing binding, leaving _auto_configured unchanged so a first-run
  config retries on the next telemetry.
- _process_telemetry() treats a status whose inverters all lack a serial
  like an empty status: the last good status is kept and it does not count
  as fresh telemetry.
- _inject_entities() publishes no per-inverter or inverter_time entities
  for serial-less inverters. EMS aggregates carry no serial in their names
  and are still published.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only GivEnergy discovery reads a unit's serial; the other hub drivers never
report one, so their inverters always arrive with serial "". Treating every
blank serial as missing would have left those sites with no inverter bound
at all. The guard now applies to GivEnergy inverters, EMS and Gateway units
only; every other type binds exactly as before.

While a GivEnergy EMS or Gateway is listed without its serial, the control
point itself is uncertain, so auto-config skips that status whatever else
it lists (including a Gateway with a single AIO) and keeps any existing
binding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ssing

A status that only lists GivEnergy units without their serial is not
counted as inverter telemetry, but it still carries device-level data.
Publish its gateway-online, EV charger and EMS aggregate entities, and
register any new EV charger straight away instead of waiting for an
inverter serial to come back. When auto-config skips a status because a
GivEnergy EMS or Gateway is unidentified, new EV chargers are registered
too, so they no longer re-trigger auto-config (and its log lines) on every
telemetry.

The missing-serial and auto-config-skipped warnings are rate limited to
once every ten minutes each, so the condition is visible in the log
without repeating on every status.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant