Feature: "Charge Deadline" strategy — be at a target by a deadline, using the cheapest hours
CONTRIBUTING.md asks for an issue before larger changes, so here is the design before I open
a PR. Working implementation is running on my own fleet (6× Marstek Venus) and
contribute/check.ps1 passes with it in place.
Related: #167 (the battery_time_remaining entity_id fix) — unrelated to this, just the first
thing I hit.
What it does
Charge to a target before a deadline, choosing when to draw from the grid:
- Goal — a target SoC, an energy reserve in kWh, or simply "batteries are full".
- Pacing — Spread evenly (constant power across a window you set) or Cheapest hours
(charge only during the periods Dynamic v2 marks low, paced across all of them).
- If the cheap periods cannot carry the energy in time, the deadline wins: the plan spreads
the remainder over the rest of the window.
- Once the target is reached it is latched for the rest of the window, optionally keeping
discharge blocked so the reserve is still there at the deadline.
The window never runs past midnight, so "full for the night" stays true and the plan never needs
tomorrow's prices (which are not published until the afternoon).
How it integrates
It does not touch the control loop. Each tick it computes a grid setpoint and delegates to
the existing Self-consumption strategy, so the PID, peak shaving and every safety limit keep
working unchanged:
required_W = energy_still_needed / charge_time_left
grid_target = clamp(required_W - measured_PV_surplus, 0, pack_limit, import_limit)
msg.target = "Self-consumption" # or the user's fallback, when idle/holding
Dispatch is the existing extension mechanism — a link in named Charge Deadline plus the
option added to the strategy selectors. No edit to 01 start-flow.json.
What it adds
|
|
node-red/02 strategy-deadline-charge.json |
38 nodes, 6 function nodes, tab Strategy Charge Deadline v4.15.0 |
home assistant/packages/house_battery_deadline_charge.yaml |
784 lines: 16 helpers, 13 template sensors, 1 binary sensor |
home assistant/packages/house_battery_control.yaml |
the strategy option in 5 selectors (11 via the &SubStrategies anchor) + 4 recorder exclusions |
home assistant/dashboard.yaml |
one view |
docs/03-strategies.md |
a ### ⏰ Charge Deadline section |
node-red/all-flows-in-one-file.json |
the tab + 36 nodes (the shared server config node is reused, global-config not duplicated) |
Every external entity it reads is either a core HBC helper or part of the documented integrator
contract in docs/09-for-integrators.md — I checked them all mechanically.
Design points I would like your view on
- Fleet SoC. HBC has no combined-SoC entity, so the package defines
sensor.hbc_battery_fleet_soc (energy-weighted across the packs, silent packs excluded
rather than counted as 0 %). It feels more like a core-package citizen than a
Charge-Deadline one — would you rather it lived in house_battery_control.yaml?
- Battery count.
range(1, 7) in that sensor and the six-entity lists in
sensor.deadline_estimated_soc are the only hard-coded counts, matching the existing
convention in house_battery_control.yaml. Happy to follow whatever you prefer long term.
- Recorder. Four sensors use
now() and so re-render every minute by design; I added them
to the exclusion list. None carries state_class, so no statistics are lost.
- Where the view goes. I put it in its own view. It could equally be folded into
Charge/Sell.
- Versioning.
bump-version.ps1 is maintainer-only, so I have not bumped anything or
written release notes. Tell me what you would like in the PR.
- Naming. File is
02 strategy-deadline-charge.json to sit next to the other strategies;
the strategy is called Charge Deadline. Shout if you would rather have them agree.
Testing
Beyond running it live: the Jinja templates render against a mocked state machine (70 assertions
— window clamping, merged price runs, malformed marks, unavailable packs) and the Compute plan
function runs against scenario fixtures in Node (35 assertions — pacing, the target-reached
latch, PV reservation, import limits, stale settings).
Next
If the shape sounds right I will open the PR. If you would prefer it split — package + flow
first, dashboard and docs after — that is easy to do.
Feature: "Charge Deadline" strategy — be at a target by a deadline, using the cheapest hours
CONTRIBUTING.mdasks for an issue before larger changes, so here is the design before I opena PR. Working implementation is running on my own fleet (6× Marstek Venus) and
contribute/check.ps1passes with it in place.Related: #167 (the
battery_time_remainingentity_id fix) — unrelated to this, just the firstthing I hit.
What it does
Charge to a target before a deadline, choosing when to draw from the grid:
(charge only during the periods Dynamic v2 marks
low, paced across all of them).the remainder over the rest of the window.
discharge blocked so the reserve is still there at the deadline.
The window never runs past midnight, so "full for the night" stays true and the plan never needs
tomorrow's prices (which are not published until the afternoon).
How it integrates
It does not touch the control loop. Each tick it computes a grid setpoint and delegates to
the existing Self-consumption strategy, so the PID, peak shaving and every safety limit keep
working unchanged:
Dispatch is the existing extension mechanism — a
link innamedCharge Deadlineplus theoption added to the strategy selectors. No edit to
01 start-flow.json.What it adds
node-red/02 strategy-deadline-charge.jsonStrategy Charge Deadline v4.15.0home assistant/packages/house_battery_deadline_charge.yamlhome assistant/packages/house_battery_control.yaml&SubStrategiesanchor) + 4 recorder exclusionshome assistant/dashboard.yamldocs/03-strategies.md### ⏰ Charge Deadlinesectionnode-red/all-flows-in-one-file.jsonserverconfig node is reused,global-confignot duplicated)Every external entity it reads is either a core HBC helper or part of the documented integrator
contract in
docs/09-for-integrators.md— I checked them all mechanically.Design points I would like your view on
sensor.hbc_battery_fleet_soc(energy-weighted across the packs, silent packs excludedrather than counted as 0 %). It feels more like a core-package citizen than a
Charge-Deadline one — would you rather it lived in
house_battery_control.yaml?range(1, 7)in that sensor and the six-entity lists insensor.deadline_estimated_socare the only hard-coded counts, matching the existingconvention in
house_battery_control.yaml. Happy to follow whatever you prefer long term.now()and so re-render every minute by design; I added themto the exclusion list. None carries
state_class, so no statistics are lost.Charge/Sell.
bump-version.ps1is maintainer-only, so I have not bumped anything orwritten release notes. Tell me what you would like in the PR.
02 strategy-deadline-charge.jsonto sit next to the other strategies;the strategy is called Charge Deadline. Shout if you would rather have them agree.
Testing
Beyond running it live: the Jinja templates render against a mocked state machine (70 assertions
— window clamping, merged price runs, malformed marks, unavailable packs) and the
Compute planfunction runs against scenario fixtures in Node (35 assertions — pacing, the target-reached
latch, PV reservation, import limits, stale settings).
Next
If the shape sounds right I will open the PR. If you would prefer it split — package + flow
first, dashboard and docs after — that is easy to do.