Skip to content

feat(plan): add a mode that forbids charging the battery from the grid - #4688

Open
romain-intel wants to merge 2 commits into
springfall2008:mainfrom
romain-intel:pr/no-grid-charge
Open

feat(plan): add a mode that forbids charging the battery from the grid#4688
romain-intel wants to merge 2 commits into
springfall2008:mainfrom
romain-intel:pr/no-grid-charge

Conversation

@romain-intel

Copy link
Copy Markdown
Contributor

Some tariffs and utility programmes forbid importing to fill the battery. Predbat has no way to express that, so the workaround I was using was to set an artificially high import rate — I was using $50/kWh.

A fake rate doesn't just block grid charging. It distorts every other number the optimiser produces: the cost of holding charge, the value of exporting, and the choice of when to sit at the reserve are all computed against a price that isn't real.

New switch.predbat_battery_charging_from_grid, on by default, so nothing changes for existing users. Turned off, Predbat won't import to fill the battery.

Enforced in three layers, because any one alone leaves a gap:

  • Planner — optimise_charge_limit only considers limits at or below the reserve, so a window can be off or a hold and nothing else. No solar is given up: a hold still charges from PV, and an off window runs in ECO mode which stores surplus anyway. A manual charge slot is downgraded to a hold rather than bypassing the restriction.
  • Execute — is_freeze_charge treats an import-requiring target as a hold, so a plan computed before the switch was turned off, or restored across a restart, can't execute an import the planner would now reject.
  • Device — optional grid_charge_enable arg, asserted every cycle via Inverter.adjust_grid_charge, and honored by the Teslemetry emulator through the Powerwall's own disallow_charge_from_grid flag. A no-op for inverters with no such control.

Negative import rates are the one exemption — being paid to import is worth taking regardless. Windows optimized as a group only qualify if every member is negative.

@springfall2008

Copy link
Copy Markdown
Owner

I can see the use of this but the implementation seems a bit confused.

Should we not have a new Predbat mode for this? I'm not sure about device level controls, isn't planner changes enough?

@romain-intel

Copy link
Copy Markdown
Contributor Author

Thanks for your feedback. I am new to this codebase and so I may not have the right intuition in it but here is what I was thinking about "mode versus flag":

  • right now the modes seem to be in a strictly nested manner: monitor controls nothing, SoC just picks a charge target, charge picks the charge target and when and finally charge & discharge add discharge windows.
  • adding a mode "do not import from grid" seems a bit orthogonal to this. If we made it as a mode, we would have to have things like "control charge (w/ grid)" and "control charge (w/o grid)" as well as "charge and discharge (w/ grid)" and "charge and discharge (w/o grid)" which seemed a bit more difficult.
  • I viewed this a bit more as a "car_charging_from_battery" type of flag (granted, that one is for the car but similar notion of where the power comes from).

On the part about the planner, you are right that the planner change would be sufficient for the correctness of the plan. The change in the execute layer was to cover the corner condition of a plan that is already computed (or restored across a restart) and the flag switched. It is indeed a very small window (since the plan would get recomputed fairly quickly).

The device one is much more separable. It was meant as a final backstop but could be stripped out because (at least for me), the "no grid charge" is already enforced at the Powerwall level.

What I can do, if that helps, is strip out the device backstop and leave the planner + execute phase. I could also strip out the execute phase but that change seems small and covers a real use case.

Let me know what you prefer.

romain-intel and others added 2 commits August 24, 2026 21:29
New switch.predbat_battery_charging_from_grid, on by default so nothing changes
for existing users. Turned off, Predbat will not import to fill the battery -
for tariffs or utility programmes that forbid it, where the workaround has been
to set an artificially high import rate. A fake rate distorts every other number
the optimiser produces: the cost of holding, the value of exporting, and the
choice of when to sit at the reserve.

Three layers:

- Planner: optimise_charge_limit only considers limits at or below the reserve,
  so a window can be off or a hold and nothing else. No solar is given up - a
  hold charges from PV and an off window runs in ECO mode, which stores surplus
  anyway. A manual charge slot is downgraded to a hold rather than bypassing it.
- Execute: is_freeze_charge treats an import-requiring target as a hold, so a
  plan computed before the switch was turned off, or restored across a restart,
  cannot execute an import the planner would now reject.
- Device: new optional grid_charge_enable switch arg, asserted every cycle via
  Inverter.adjust_grid_charge, and honoured by the Teslemetry emulator through
  the Powerwall's own disallow_charge_from_grid flag. A no-op for inverters with
  no such control.

Negative import rates are the single exemption - being paid to import is worth
taking regardless. Windows optimised as a group only qualify if every member is
negative.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit d8da2784b3796dcba67da4648e28559dfb7b20ca)
… charge

clip_charge_slots runs after the window optimiser and raises a charge limit to
the full battery in three cases, so that adjacent windows share a limit and can
be merged. With battery_charging_from_grid off that quietly reintroduces exactly
what allow_grid_charge_window excluded: a hold at a full battery came back out as
a 100% charge limit.

Seen on a live system as 497 hits in under ten hours of the execute-layer guard
downgrading "Charge target 100% above SoC" back to a hold - always exactly 100%,
which is the signature of the clip-up rather than of the optimiser. No grid
charge ever happened, since the guard caught every one, but the plan read as a
charge and execute was doing work the planner should have made unnecessary.

The clip-up is now skipped while grid charging is disabled. Nothing changes for
anyone with the mode on, which is the default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit a3c94e9a8a0f7eb82aa56020323ad6ffb3f9d7c3)
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.

2 participants