Feature Description
Allow operators to pin a temporary battery setpoint to one specific battery driver, instead of always applying it to the whole site battery pool.
Default remains pool / aggregate control. Single-battery scope is an explicit opt-in at control time (UI + API).
Motivation
Community use case (Sourceful FTW Discord / #support):
- Multi-battery site
- One pack's internal cells are unbalanced
- Operator wants to charge only that pack toward ~100% during high PV so the BMS can rebalance
- Current
charge mode and /api/battery/manual_hold act on the aggregate pool, so all online batteries start charging
Today's workarounds (priority, temporary driver offline, vendor app) are clumsy for a common service/balancing task.
Proposed Solution
Extend manual hold with an optional driver scope:
API
POST /api/battery/manual_hold
{
"direction": "charge",
"power_w": 5000,
"hold_s": 900,
"driver": "battery_a"
}
driver optional; omit/empty = pool (current behavior)
- GET returns the same
driver field when scoped
- Scoped semantics: target power on that battery only; other online batteries held at 0 W
- Existing SoC / per-driver caps / slew / fuse guard still apply
- Unknown driver → 400
- Expiry / DELETE clears hold and returns to selected site mode
UI
In <ftw-battery-control>:
- Scope: All batteries (default) | selected battery
- When opened from a battery card/planet, preselect that driver
- Active-hold banner shows scoped driver
Control loop
In ComputeDispatch manual-hold path:
- no driver → existing aggregate distribute
- with driver → scoped targets (hold power on that driver, 0 on others) then existing clamp/slew/fuse pipeline
Alternatives Considered
priority mode — still site distribution, awkward for one-off balancing
- Temporarily disable other drivers — drops telemetry, error-prone
- Vendor app full-charge — breaks FTW closed-loop
- Permanent per-battery modes — larger product surface than needed
Non-goals
- Permanent independent operating modes per battery
- Planner/MPC optimizing packs separately as first-class products
Scope / Effort Estimate
Medium — go/internal/control, go/internal/api battery manual hold, web/components/ftw-battery-control.js, unit tests.
Additional Context
- Site-EMS by design; multi-battery split is internal (
priority/weighted)
- Manual hold already bypasses planner/idle short-circuits but keeps safety clamps — best extension point
- Discord: Fredrik approved capturing as a feature and starting a PR
Feature Description
Allow operators to pin a temporary battery setpoint to one specific battery driver, instead of always applying it to the whole site battery pool.
Default remains pool / aggregate control. Single-battery scope is an explicit opt-in at control time (UI + API).
Motivation
Community use case (Sourceful FTW Discord / #support):
chargemode and/api/battery/manual_holdact on the aggregate pool, so all online batteries start chargingToday's workarounds (
priority, temporary driver offline, vendor app) are clumsy for a common service/balancing task.Proposed Solution
Extend manual hold with an optional driver scope:
API
POST /api/battery/manual_hold{ "direction": "charge", "power_w": 5000, "hold_s": 900, "driver": "battery_a" }driveroptional; omit/empty = pool (current behavior)driverfield when scopedUI
In
<ftw-battery-control>:Control loop
In
ComputeDispatchmanual-hold path:Alternatives Considered
prioritymode — still site distribution, awkward for one-off balancingNon-goals
Scope / Effort Estimate
Medium —
go/internal/control,go/internal/apibattery manual hold,web/components/ftw-battery-control.js, unit tests.Additional Context
priority/weighted)