What
DualSungrowCoordinator.force_charge splits requested charge power across the two inverters proportionally but never clamps each share to that inverter's hardware charge-power limit. The sibling force_discharge method does clamp.
File: custom_components/power_sync/optimization/coordinator.py
force_charge (≈L4618):
async def force_charge(self, duration_minutes: int = 30, power_w: float = 0) -> bool:
if power_w > 0:
p1, p2 = await self._split_power(power_w / 1000, prefer_lower_soc=True)
r1 = await self._coord1.force_charge(duration_minutes, power_w=p1 * 1000)
r2 = await self._coord2.force_charge(duration_minutes, power_w=p2 * 1000)
force_discharge (≈L4629) — note the guard that is absent from force_charge:
if power_w > 0:
max_split = self._max_split_kw("discharge")
if max_split and (power_w / 1000.0) >= sum(max_split):
p1, p2 = max_split
else:
p1, p2 = await self._split_power(power_w / 1000, prefer_lower_soc=False)
_split_power (≈L4447) is SOC-proportional only and applies no power cap.
Why it matters
When the optimizer requests a high charge power and the SOC split is skewed (one inverter much emptier), the proportional share for one inverter can exceed its battery_max_charge_power_w. The hardware will either reject the command or clip it silently, so the requested aggregate charge power is not actually delivered — the charge schedule under-fills versus the LP plan. The discharge path was hardened against exactly this; the charge path was missed, so the protection is asymmetric.
Fix
Mirror the discharge guard in force_charge: add a _max_split_kw("charge") helper call (or reuse _max_split_kw with direction="charge") and cap to the per-inverter limits when power_w/1000 >= sum(max_split), falling back to _split_power otherwise. Additionally, _split_power should clamp each leg to its per-inverter limit so the proportional path is also safe.
Note: this code is shared with upstream (bolagnaise) and the defect likely exists there too; fork-tracking here.
Migration source: PLA-76
Linear status at cutover: Backlog
Cut over to GitHub on 2026-07-22.
What
DualSungrowCoordinator.force_chargesplits requested charge power across the two inverters proportionally but never clamps each share to that inverter's hardware charge-power limit. The siblingforce_dischargemethod does clamp.File:
custom_components/power_sync/optimization/coordinator.pyforce_charge(≈L4618):force_discharge(≈L4629) — note the guard that is absent from force_charge:_split_power(≈L4447) is SOC-proportional only and applies no power cap.Why it matters
When the optimizer requests a high charge power and the SOC split is skewed (one inverter much emptier), the proportional share for one inverter can exceed its
battery_max_charge_power_w. The hardware will either reject the command or clip it silently, so the requested aggregate charge power is not actually delivered — the charge schedule under-fills versus the LP plan. The discharge path was hardened against exactly this; the charge path was missed, so the protection is asymmetric.Fix
Mirror the discharge guard in
force_charge: add a_max_split_kw("charge")helper call (or reuse_max_split_kwwithdirection="charge") and cap to the per-inverter limits whenpower_w/1000 >= sum(max_split), falling back to_split_powerotherwise. Additionally,_split_powershould clamp each leg to its per-inverter limit so the proportional path is also safe.Note: this code is shared with upstream (bolagnaise) and the defect likely exists there too; fork-tracking here.
Migration source: PLA-76
Linear status at cutover:
BacklogCut over to GitHub on 2026-07-22.