Repository navigation
Conversation
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>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Address the critical topology-selection issue, stale telemetry handling, and warning rate limiting.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Fixes GivEnergy gateway handling when inverter serials are temporarily missing.
Changes:
- Filters unidentified units from binding and entity publication.
- Preserves topology detection, telemetry, and EV registration.
- Adds regression tests and warning throttling.
| File | Summary |
|---|---|
apps/predbat/tests/test_gateway.py |
Adds coverage for missing serials, topology, EV handling, and compatibility. |
apps/predbat/gateway.py |
Implements serial filtering and telemetry handling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if len(addressable) < len(inverters): | ||
| self.log(f"Warn: GatewayMQTT: ignoring {len(inverters) - len(addressable)} GivEnergy inverter(s) reported without a serial") | ||
| inverters = addressable |
There was a problem hiding this comment.
Automated reply from the triage bot.
Already addressed in 16c8b90, which was pushed after this review was posted.
gateway.py:1190-1198 now counts unidentified GivEnergy AIOs towards the topology decision, by id() rather than membership (protobuf messages compare by value, so two blank entries would otherwise collapse into one), while the binding filter at gateway.py:1223 still drops them from addressable.
For the shape named here — Gateway + one real AIO + a serial-less AIO with no battery block — aios is [real AIO] and unidentified_aios is [blank AIO], so topology_aio_count is 2 and the Gateway is selected; it is then bound because it carries its own serial.
Covered by test_blank_serial_aio_with_no_battery_still_counts_towards_gateway_topology, which constructs the blank AIO with no battery block set at all and asserts the Gateway suffix is what gets bound.
No further change was made for this thread. The branch has since been merged with current main (5210f42); pre-commit and the gateway suite (290 tests) pass on the merge result.
A not-yet-identified GivEnergy AIO usually reports no battery block at all (it is still mid-handshake), so it was invisible to the `aios` count used to decide whether a Gateway should be the control point. Whether the Gateway or the real AIO got bound then depended on whether the unidentified unit happened to report battery data on that particular telemetry tick — exactly the class of mis-binding this control-point selection exists to prevent. Count unidentified AIOs towards the topology decision by identity (protobuf messages compare by value with `in`, so two blank units would otherwise collapse into one), while still excluding them from the addressable list used for binding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ank-serial # Conflicts: # apps/predbat/gateway.py
|
Automated comment from the triage bot (PR cleanup). Merged current Checked where the two sides interact:
Results on the merge commit: No review feedback was outstanding. The one Copilot thread already has a reply (fixed in 16c8b90), and CI was green before the merge. |

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, andsoc_maxfell back to its default with asoc_max element Nonevalidation error._inject_entities()publishedpredbat_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()):_needs_reconfigure()never treats it as a new inverter.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._auto_configuredare 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.inverter_timeentities, so nopredbat_gateway__*entities appear. EMS aggregates carry no serial in their names and are still published.Tests
All in
TestGatewayUnitControlBinding, most driven through_process_telemetry():automatic_config()keeps the binding and warns."": these still configure as before.On
mainall but the non-GivEnergy test fail (that one checksmain'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 gatewaypasses (284) and./run_all --quickpasses.🤖 Generated with Claude Code