Skip to content

docs(inverter-setup): fix Sigenergy Freeze Charging forced-import bug - #4392

Merged
springfall2008 merged 4 commits into
springfall2008:mainfrom
chalfontchubby:docs/sigenergy-freeze-charge-fix
Jul 31, 2026
Merged

docs(inverter-setup): fix Sigenergy Freeze Charging forced-import bug#4392
springfall2008 merged 4 commits into
springfall2008:mainfrom
chalfontchubby:docs/sigenergy-freeze-charge-fix

Conversation

@chalfontchubby

@chalfontchubby chalfontchubby commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

discharge_cut_off_state_of_charge was hardcoded to 100 for Freeze Charging, which is always above current SoC by construction. Sigenergy have confirmed this is a firmware bug on their side: setting this above current SoC makes the inverter actively import from grid to reach it, even with grid_import_limitation at 0kW. Not a trickle in practice - observed several kW, continuing unattended until the target is reached (see #4375 for the full live-hardware investigation).

  • Pin discharge_cut_off_state_of_charge once, when Freeze Charging starts, to current SoC minus a small (1pp) margin - not a hardcoded value, and not continuously re-pinned.
    • Why once, not continuous: Freeze Charging's own contract already means SoC can only stay flat (grid covers any shortfall) or rise (solar surplus still charges the battery) while discharge is genuinely blocked - so a one-time pin at freeze-start remains valid for the whole session. Continuously re-pinning to live SoC would actively undermine "frozen": it relocates the anchor to wherever SoC has already drifted to, rather than correcting back to it, so real drift (e.g. the inverter's own standby losses) would never actually get corrected.
    • Why the small margin: the confirmed mechanism only ever corrects one way (import if SoC reads below target). Pinning to the exact SoC reading makes ordinary sensor noise asymmetric - a low reading costs a real, if tiny, import; a high reading costs nothing. The margin absorbs normal jitter without needing to track it.
  • Apply the same one-time-pin treatment to Freeze Discharging's charge_cut_off_state_of_charge (mirrored: current SoC plus a small margin), instead of leaving it hardcoded to 0. Not itself vendor-confirmed the way the discharge side is, but 0 is always below current SoC by the same structural shape as the confirmed bug - and pinning costs nothing, since it still fully blocks charging either way. This also restores what jamiekets independently proposed in Configure Predbat to control Sigenergy SigenStor inverter #2077 back in November 2025 (both cutoffs pinned together) - the version that made it into the shipped docs only kept half of that.
  • Add an explicit note explaining the firmware bug, why the pin must be fixed rather than tracking, and why the margin exists - linking Sigenergy: Freeze Charging template still forces grid-import at full price (discharge cutoff=100 in Maximum Self Consumption) — related to #2872 #4375 and the wider community setup thread (Configure Predbat to control Sigenergy SigenStor inverter #2077, where Sigenergy's own acknowledgement is quoted).
  • Fix a pre-existing YAML indentation bug in the docs snippet - automation_sigen_ess_max_charging_limit_input_number_action was nested two spaces too deep, making it a child of the first automation's actions instead of a sibling automation. Caught by parsing the snippet directly with PyYAML.

A more advanced variant - ratcheting the target up only in response to confirmed solar surplus over each period (comparing PV/load totals as the trigger, rather than using the SoC reading itself to decide, so it's immune to sensor noise entirely) is being tested live but deliberately not included here - docs should stay simple and proven, not experimental.

Test plan

  • ./run_pre_commit (via coverage/run_pre_commit) - all hooks pass
  • Full automation YAML snippet parses correctly with PyYAML (3 top-level automations, correctly structured)
  • sensor.sigen_plant_battery_state_of_charge confirmed to exist and report correctly on live hardware
  • Core fix (pinning discharge_cut_off_state_of_charge to current SoC) live-tested on real Sigenergy hardware, root cause confirmed via controlled testing - see Sigenergy: Freeze Charging template still forces grid-import at full price (discharge cutoff=100 in Maximum Self Consumption) — related to #2872 #4375
  • Live-observed tonight during a real Freeze Charging window: SoC held steady (not climbing toward a stale target) while grid import correctly tracked only real house load - matches expected behaviour, contrasts with the pre-fix bug
  • Docs-only change, no code touched

Closes #4375

discharge_cut_off_state_of_charge was hardcoded to 100 for Freeze
Charging, which is always above current SoC by construction - Sigenergy
has confirmed this is a firmware bug: setting this above current SoC
makes the inverter actively import from grid to reach it, even with
grid_import_limitation at 0. Not a trickle in practice - observed several
kW, continuing unattended until the target is reached. Root-caused and
fix confirmed via live testing (springfall2008#4375), and
independently corroborated in the community setup thread (springfall2008#2077,
Sigenergy's own acknowledgement quoted there from Feb 2026).

- Pin discharge_cut_off_state_of_charge to current SoC instead of a
  hardcoded value in the main mode-bridge automation.
- Add a second automation that re-pins continuously while Freeze
  Charging is active, not just on mode change - a long session with
  house load exceeding solar (the situation Freeze Charging exists for)
  would otherwise let SoC drift below a stale pin and reopen the same
  gap. grid_import_limitation is not a reliable backstop against this.
- Add an explicit note explaining the firmware bug and why
  grid_import_limitation alone doesn't fix it.
- Fix a pre-existing YAML indentation bug in the docs snippet
  (automation_sigen_ess_max_charging_limit_input_number_action was
  nested two spaces too deep, making it a child of the first
  automation's actions instead of a sibling automation - caught by
  parsing the snippet directly).

Live-tested on real Sigenergy hardware (springfall2008#4375).
…d zone

Revises the previous commit in this PR after further review:

- Drop the continuous re-pinning automation. It was based on a flawed
  premise (that SoC drifting down during a long Freeze Charging session
  needs tracking) - but Freeze Charging's own contract already covers
  this: any load shortfall is met from grid, not the battery, so SoC
  can only stay flat or rise while discharge is genuinely prohibited. A
  one-time pin at freeze-start stays valid for the whole session by
  construction. Continuous re-pinning actively undermines "frozen" -
  it relocates the anchor to wherever SoC has already drifted to
  instead of correcting back to it, so real drift (e.g. inverter
  standby losses) would never get corrected.
- Add a small (1 percentage point) margin to the pinned value. The
  confirmed firmware mechanism only ever corrects one way (import if
  SoC reads below target), so ordinary sensor-reading noise around an
  exact-SoC pin is not self-cancelling - a low reading costs a real
  (if tiny) import, a high reading costs nothing. The margin absorbs
  normal jitter without needing to track it.
- Apply the same one-time-pin treatment to Freeze Discharging's
  charge_cut_off_state_of_charge, mirroring the discharge side instead
  of leaving it hardcoded to 0. This restores what jamiekets
  independently proposed in springfall2008#2077 back in November 2025 (both sides
  pinned together) - the version that shipped only kept half of it.
  Not itself vendor-confirmed the way the discharge side is, but 0 is
  always below current SoC by the same structural shape as the
  confirmed bug, and pinning costs nothing (still fully blocks
  charging either way).

A more advanced variant - ratcheting the target up only in response to
confirmed solar surplus over each period (using PV/load totals as the
trigger, not the SoC reading itself, to avoid noise entirely) - is
being tested live but is not included here; docs should stay simple
and proven. Noted as a future direction in the text and on springfall2008#4375.
…around safety

- Revert charge_cut_off_state_of_charge for Freeze Discharging back to
  a plain hardcoded 0. The mirrored-bug concern was speculative (never
  vendor-confirmed the way the discharge-side bug was), and 0 has been
  in real use across the wider community template for months without
  anyone reporting the kind of dramatic, easily-noticed symptom a real
  mirrored bug would produce - not worth the added complexity without
  evidence.
- Add a note explaining why discharge_cut_off's margin is clamped at 0
  rather than left to go negative: it's an unsigned 16-bit Modbus
  register on the wire, and the integration's own write encoding has
  no guard against a negative value - it would silently wrap around
  into a huge, nonsensical raw value rather than being rejected.
Live-observed (2026-07-30) that 1pp was more generous than needed. The
pin is one-time (set once when Freeze Charging starts, not
continuously re-sampled), so the margin only ever has to cover
possible imprecision in that single initial reading - not ongoing
noise across a session. 0.25pp is enough for that.
@springfall2008
springfall2008 merged commit da4c4cd into springfall2008:main Jul 31, 2026
2 checks passed
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.

Sigenergy: Freeze Charging template still forces grid-import at full price (discharge cutoff=100 in Maximum Self Consumption) — related to #2872

2 participants