Skip to content
David Cumps

Victron Dynamic ESS

  • Cumps
  • 2025-2026

I run a Victron Energy battery system at home, and it’s driven by a Node-RED flow I wrote myself rather than Victron’s own scheduling. Every 5 seconds it decides whether the battery should charge, discharge, or sit idle, based on the Belgian day-ahead (Nordpool) electricity price and VRM’s solar and consumption forecasts, and writes the resulting setpoint straight to the Victron Venus device. The whole thing runs as a single Node-RED tab on the Cerbo GX that already sits in the installation, so there’s no extra hardware.

The control loop. The decision logic lives in one function node with eight outputs, reading prices, forecasts, battery state and user knobs from globals and writing out the grid setpoint, the feed-in cap, and everything the dashboard shows. It ranks each of the day’s quarter-hours by price and charges on the cheapest, discharges on the most expensive, and leaves everything else idle; as of August 2026 that ranking runs across a merged 48-hour window of today and tomorrow’s prices, not just today. Feed-in is curtailed below a threshold derived from the actual injection formula in my energy contract, so the system only ever exports when it’s worth it. This is the second iteration of the flow: the original split the same logic across seven function nodes and a tangle of wires, and the rewrite replaced that with one function and three independent retry chains, one per API it depends on, each with exponential back-off.

Keeping the battery healthy. A separate once-a-minute counter tracks how long it’s been since the battery last reached a fully balanced state (≥99% state of charge with negligible DC power). If that stretches past roughly 34 days, which can happen through a low-solar winter, the flow adds extra forced-charge hours until the pack balances again. Every reading it depends on (battery percentage, DC power, VRM forecasts) carries a freshness timestamp, and stale data is treated as “unknown” rather than trusted, so a dropped sensor can’t quietly keep driving decisions.

Talking to the grid and the house. Part of my solar is AC-coupled and sits outside the Victron system entirely, so Victron can throttle its own charge controller but can’t curtail that part directly. The only way to stop it exporting at a loss on a negative-price afternoon is to make sure the battery has room to absorb it, which means discharging earlier than the price ranking alone would suggest. I shipped that solar-aware discharge reservation in August 2026. Curtailment also backs off automatically whenever the EV is charging, since exporting at a small loss is still cheaper than refusing to export at all while the car needs power. State that matters across restarts, such as the battery-age counter, the day’s price schedule and the user’s settings, is written to the Cerbo’s flash on a two-minute flush interval, so it survives a Node-RED crash, a manual restart, or a Venus OS firmware update without falling back to defaults.

Dashboard and visibility. The Node-RED dashboard shows the live setpoint, today and tomorrow’s price curves, a sortable price table with the charge and discharge quarters highlighted, the forecast numbers behind the day’s plan, and battery health. The price chart is a self-hosted Chart.js instance rather than the stock dashboard widget, because I wanted a shaded curtailment band and an overlay showing exactly which quarters the controller plans to sell into, drawn from the same plan the controller itself commits to, not a recomputation that could drift from reality.

Technologies: Node-RED, JavaScript function nodes, Victron Venus OS (Cerbo GX), Victron D-Bus, VRM API, Nordpool day-ahead API, Node-RED Dashboard, Chart.js, Node-RED contextStorage (file-backed persistence).

You May Also Like