Skip to content

Sigenergy: Predbat and Axle contend for the inverter — VPP mode evicts Axle's NBI dispatch #4454

Description

@mgazza

Summary

On a Sigenergy, Predbat's VPP mode and Axle's NorthBound Interface (NBI) are mutually exclusive — the inverter accepts one controller at a time. Today Predbat wins that contest by accident rather than by design, and evicts Axle mid-dispatch.

_manage_vpp_registration() in apps/predbat/sigenergy.py runs on the 5-minute poll tick and, whenever the system is not in VPP and the read-only switch is off, switches it straight back to VPP. It has no awareness of Axle at all (grep -ci axle apps/predbat/sigenergy.py → 0).

Evidence

From a live system on 2026-08-06 (identifiers redacted — this is a public repo). Axle had an export event scheduled 20:00–21:00 local, 100 p/kWh, which Predbat had already ingested as its own export window via load_axle_slot().

Operating-mode timeline:

19:35:18  ->  Northbound Integration      (Axle takes the inverter)
19:40:12  ->  VPP                          (back to Predbat)

Exactly one SIGENERGY_POLL_INTERVAL. Meanwhile, once per minute:

19:35:54 Warn: SigenergyAPI: System <redacted> is not in VPP mode
         (Northbound Integration) — controls skipped until onboard is approved
... 19:36:54, 19:38:01, 19:39:01, 19:40:01

The user's report was "it changed to NBI Scheduling and then went back to VPP" — with the reasonable follow-up question of why Axle appeared to own a mode that is actually Predbat's.

Two problems

1. Nobody decided who owns the inverter. Predbat wins on a 5-minute timer. It's not obviously wrong — Predbat plans the Axle session as its own export window, so the event still delivers — but it's a race, it's silent, and it overrides whatever Axle had scheduled.

2. axle_control exists and doesn't reach this component. Fetch.fetch_config_options() (apps/predbat/fetch.py:2408) already raises set_read_only_axle when axle_control is set and an Axle event is active. But that flag is only consumed in execute.py:63 and output.py:955. sigenergy.py reads the user-facing switch.predbat_set_read_only entity instead, so even with axle_control: true the component would still reclaim VPP and evict Axle. The stand-down feature does not work on Sigenergy.

Side effect worth fixing either way

While in NBI, onboard_status is derived as pending_approval. Downstream UIs render that as "approve this in the Sigenergy app" — so every Axle event raises a false onboarding alarm at a system that is fully onboarded. Both PRs below fix this.

Proposed alternatives

Two mutually-exclusive draft PRs. Only one should merge.

  • A — Predbat owns the inverter, deliberately. Keeps today's outcome, removes the race: reclaim VPP every 60s instead of 300s, log the reclaim naming the controller displaced, expose contended_by on the status sensor. No config change; applies to everyone.
  • B — Axle owns the inverter during its events. Wires the existing axle_control flag into the component: leave the mode untouched during an event (including not dropping to MSC, which would hand control to the owner's app rather than Axle), suppress battery commands for that window, reclaim VPP when it ends. Dormant unless axle_control: true.

The question that likely settles it

Does Axle credit delivery from meter data, or from its own NBI dispatch completing? If the latter, option A costs the customer the payment even though the energy flowed.

Scope

3 of 6 active Sigenergy systems in our fleet hit this in a single 20-hour window. It is structural and recurs on every Axle event. This is the Sigenergy expression of the same dual-control contention previously seen on GivEnergy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions