Skip to content

[Bug] SolarFlow AC/AC+ batteries failing to start charging due to mathematical sign conflict in power distribution logic #1230

Description

@thib3113

Home Assistant Version

2026.3.4

Zendure Integration Version

1.2.7-pre3

Describe the bug

I have observed a bug where my SolarFlow batteries (sf2400ac and sf2400ac+) remain in INACTIVE state even when there is a significant solar surplus (over 1000W of grid injection). The batteries stay in "Idle" mode and refuse to transition to "Charge" mode because the internal power distribution logic fails to handle the negative sign convention used by these AC models.

To Reproduce

  1. Have a SolarFlow AC or AC+ battery in INACTIVE state (Idle).
  2. Configure the device with a fusegroup (in my case: "Device has its own circuit or phase" for each battery).
  3. Generate a solar surplus (negative P1 value) between 0W and 1200W.
  4. Observe that the battery remains in INACTIVE state and does not start charging, despite the surplus being available.
  5. The system only recovers if the surplus exceeds ~1200W or after a long delay via the "50W pulse" fallback mechanism.

Expected Behaviour

The batteries should immediately transition to CHARGE mode as soon as a negative surplus (injection) is detected on the P1 meter, respecting the SmartMode.POWER_START threshold.

What device are you using?

  • SF2400 AC
  • SF2400 AC+

Log Extracts

2026-03-25 09:13:50 INFO (MainThread) [custom_components.zendure_ha.manager] P1 ======> p1:-1069 isFast:False, setpoint:-1069W stored:0W
2026-03-25 09:13:50 INFO (MainThread) [custom_components.zendure_ha.manager] Charge => setpoint -1069W
2026-03-25 09:13:50 DEBUG (MainThread) [custom_components.zendure_ha.manager] Device sf2400ac 063 state: INACTIVE, SOC: 12%, homeInput: 0W, homeOutput: 0W
2026-03-25 09:13:50 DEBUG (MainThread) [custom_components.zendure_ha.manager] Adding sf2400ac 063 to idle list

Other Information

The "HTTP Timeout Rescue" (Strange Recovery)

Ironically, in my case, the only thing that eventually "saves" the system and starts the charge is a communication bottleneck on the battery side. The internal ESP processor sometimes fails to keep up with the volume of requests, leading to timeouts.

  1. The system is stuck in the 0W bootstrap loop.
  2. It sends periodic -50W pulses to "test" the idle batteries.
  3. Multiple HTTP TimeoutErrors (due to the ESP being overwhelmed) delayed the state update for over a minute.
  4. When a request finally succeeded, it reported a 49W consumption.
  5. This 49W value (from the pulse) is what finally moved the battery to the charge list, breaking the 0W capping logic.
    Without these communication timeouts and the 50W fallback, the system would likely stay stuck at 0W indefinitely.

Evidence: Timeline of the failure (2026-03-25)

Time (Local) Solar Prod House Cons Net Export Battery State Battery Charge Status
09:13:45 1116 W 56 W -1060 W INACTIVE 0 W Stuck (Bug)
09:13:50 1116 W 56 W -1060 W INACTIVE 0 W Stuck (Bug)
09:13:55 1116 W 56 W -1060 W INACTIVE 0 W Stuck (Bug)
09:13:57 1116 W 56 W -1060 W ACTIVE -1019 W Recovered via Fallback

Technical Analysis: The "1200W Invisible Wall"

For sf2400ac models, the integration defines a "Charge Limit" of -2400W. The manager calculates an "Optimal Charge" (1/4 of the limit), resulting in -600W.
The wake-up condition if dev_start < 0 is never met as long as the surplus is less than 1200W because:
dev_start = Surplus(-1000) - (Optimal_Charge(-600) * 2) = +200 (which is > 0).

graph TD
    A[Surplus Detected: -1000W] --> B{Are batteries charging?}
    B -- No --> C[Calculate dev_start threshold]
    C --> D["dev_start = Surplus - (Optimal_Charge * 2)"]
    D --> E["dev_start = -1000 - (-600 * 2) = +200"]
    E --> F{Is dev_start < 0?}
    F -- No (+200) --> G[STAY IN IDLE]
    F -- Yes --> H[WAKE UP BATTERY]
    G --> I[Invisible Wall: No charging starts]
Loading

Affected Code (manager.py)

# custom_components/zendure_ha/manager.py:531
limit = self.charge_limit # is 0 if no batteries are charging
setpoint = max(limit, setpoint) # max(0, -1069) = 0W. Surplus is deleted.

# custom_components/zendure_ha/manager.py:558
if (dev_start := dev_start - d.charge_optimal * 2) >= 0: # logic fails with negative optimal
    break

Proposed Fix

  1. Use Absolute Values: Update formulas to use abs(charge_optimal) to handle both positive and negative sign conventions.
  2. Protect Setpoint during Bootstrap: Allow the setpoint to remain negative even if the current active limit is 0, as long as idle batteries are available.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions