Voluntary Frequency Response (VFR)
A request for comments, before I write any code: one rule to make a home battery a good grid citizen
Five weeks ago I wrote about the evening Britain's grid ran out of margin — the forty-eight minutes on 23 June when frequency sat below 49.8 Hz, bottoming out at 49.657 just after eight, while NESO halted exports to the Netherlands and phoned Paris. I watched most of it unfold from the sofa, which is the correct place to watch a grid event from. What I keep coming back to, though, is what was happening in my garage. Twenty kilowatt-hours of battery — fully charged since dawn, the most flexible asset I own — sat through the grid's worst evening in years doing absolutely nothing. NESO files the whole week under "a complex operational challenge", which is control-room for we'd rather not do that again.
My battery sat it out because it is frequency-blind, and so is nearly every battery and EV charger in the country. It can see the clock and it can see the price. It cannot see the one signal that says, second by second, whether the grid is struggling — even though the inverter it lives behind measures grid frequency continuously and would tell it for free. So it charges like a caveman: at midnight it steps from nothing to 3.5 kW, holds that until the battery fills — sometimes three in the morning, sometimes five — then steps back to nothing. Everyone else's kit steps on at the same minute mine does, and the frequency trace shows it: a sag at midnight as the herd arrives, a lift around half five as the all-nighters drop off. I'm told these window-edge wobbles are a growing nuisance in the control room, and they'll get worse as more of us land on time-of-use tariffs.
The patient, as VRM sees it. Two details matter for what follows: the grid frequency reading (49.8 Hz as I took this), and the Multi already sitting in "External control" — the hook this whole design hangs off.
I'd like to fix my corner of this, and before I write any code I'd like people who know more than me — about frequency response, Victron kit, Home Assistant, Kraken's APIs, FIT rules — to tell me what's wrong with the plan. Objections are cheap now and expensive later.
One rule
The whole design is one rule. My bills define a floor — the energy the battery must hold, or acquire cheaply, to get the house through the day. Everything above that floor follows grid frequency.
I'm calling it voluntary frequency response — VFR. Contracted to nobody, paid by nobody on purpose, capped in what it can cost me, given freely. Pilots will note that flying VFR means looking out of the window and taking responsibility for your own separation, which feels about right.
What the rule does all day
Overnight, the floor is a charging trajectory. Say I need 14 kWh by six. The controller tracks the required rate — energy still needed divided by cheap hours remaining — and modulates around it with frequency:
required_rate = energy_still_needed / cheap_time_remaining
charge_power = clamp(required_rate + margin + k × (f − 50), floor, 3.5 kW)Every kWh in the window costs 2.99p whether I take it at 3.5 kW or 1.5 kW, so this flexibility is genuinely free, and I can give all of it away. Frequency high: charge harder, absorb the surplus. Frequency low: ease off. It's self-reinforcing in a way I find pleasing — high-frequency spells bank energy early, which lowers the required rate, which buys more room to turn down later. And if the night goes badly the envelope simply collapses to flat-out charging, which is to say: my worst case is my present.
There's a happy asymmetry buried in here. Idle, the most I can offer a struggling grid is my 2.8 kW discharge limit. Mid-charge, merely easing off to zero is a 3.5 kW response. A charging battery is worth more to the grid than an idle one, and it costs nothing to deliver.
During the day, the floor is a reserve — the energy needed to reach midnight without buying peak electricity. Anything above that line is surplus, and surplus turns out to be nearly free to spend, thanks to a tariff quirk I'd never properly looked at. My legacy solar FIT pays about 8p a kWh for export. Refilling overnight costs 2.99p — call it 3.5p after round-trip losses. That margin isn't enough to cycle the battery on purpose (there's wear, and a stored kWh's proper job is displacing 25p peak import, which is why today I only ever export to spill solar). But it does mean that exporting surplus at moments of low frequency costs me roughly nothing, and might make me a shade. Sunny days build lots of surplus and lots of response; a grim still January day builds none, and the controller keeps quiet. Nobody is worse off on any given day.
The one exception to the rule is the emergency. Below 49.8 Hz — the edge of normal operation — stop charging, whatever the trajectory says. Somewhere deeper (more on exactly where in a moment) discharge into the grid regardless of the floor, down to a protected state of charge, with per-event and per-month caps so the worst-case annual cost of citizenship is pence. Above 50.2 Hz, charge flat out at any price — barely a sacrifice, since I keep the energy.
Rerunning 23 June
The event hit at eight in the evening, so the overnight logic had nothing to offer: no charging to throttle. But a June day's solar had left the battery stuffed with surplus, and the daytime rule would have swung the house from importing about a kilowatt to exporting 2.8 — a three-to-four-kilowatt change in grid position, held for the whole forty-eight minutes. Call it 2.2 kWh delivered at the worst moment of the grid's year. The FIT would have paid me about 18p against 7p of refill. France got a phone call; I'd have got change for a coffee.
The rerun also caught a mistake before I could make it. My instinct for the emergency discharge trigger was 49.6 Hz — comfortably outside normal operations, comfortably inside the statutory 49.5 floor. The year's biggest event never went below 49.657. A 49.6 trigger sleeps through the whole thing. So the trigger belongs nearer 49.7, and 23 June graduates from motivation to acceptance test: a controller that sleeps through the year's biggest event is mistuned by definition.
Not becoming the herd
The midnight sag exists because control is clock-triggered, and it deepens with every install: each new battery on an off-peak tariff makes the same step at the same minute. Frequency-following inverts that. If the herd's arrival drags the frequency down at midnight, my controller sees it and holds back, then charges in the herd's wake a few minutes later. Each household that follows frequency adds damping to the very signal it reads — the more of us there are, the smoother the edge gets. It's the mechanism generator fleets have used to share load for a century, just applied at the other end of the wire.
There's a blunter version of this idea already in my garage: my OpenEVSE charger has a "randomise start time" setting, a random delay so that fleets of them don't step onto the grid in unison. Same diagnosis, cruder medicine — randomisation smears the herd's edge but responds to nothing. Frequency-gating desynchronises and puts the flexibility to work, because the households holding back are exactly the ones holding back when it matters. (A little randomness still belongs in the design — each install should jitter its thresholds a few percent so the pattern can't become its own herd.)
At the far edge my current setup has accidental camouflage — it stops when the battery fills, not when the tariff ends, so the step-off already lands at a different minute each night. But it's still a 3.5 kW step. Under the rule the trajectory aims to finish about 05:40 and tapers out, keeping the last twenty minutes as catch-up reserve: a glide rather than a drop, and never part of the pile-up at the window's end.
I want to be honest about scale, because a cynic would rightly say none of this matters. One battery is a rounding error. But a hundred thousand houses running this pattern is roughly 280 MW of response — the size of contracts NESO actually procures — and unlike the clock-triggered herd, this fleet gets better-behaved as it grows. That, more than my 2.8 kW, is why I'm publishing the design before building it. A pattern is a power station.
Where the loop lives, and other engineering
One design question I genuinely can't settle alone: where should the control loop run?
On the Victron GX device itself — Node-RED or a small D-Bus service. Detection to command in under a second; perhaps 1–2 s to full response including inverter ramp. But it means installing software on the box that runs the inverter, and in my experience the Cerbo GX is easy to gum up and crash. A controller that destabilises its own inverter's brain is an ugly failure mode.
In Home Assistant over Modbus TCP. More like 2–5 s to full response, and the loop dies whenever HA restarts — so a watchdog that reverts the Victron to its stock behaviour is non-negotiable. In exchange, nothing touches the GX, and a controller crash strands me in today's behaviour rather than a broken inverter.
My lean is Home Assistant, because for this design latency barely matters. The overnight logic works in minutes; grid events build over seconds and persist for tens of seconds to minutes, and arriving three seconds late to a ninety-second excursion still delivers most of the value. Sub-second response only matters for contracted Dynamic Containment, which this explicitly isn't. Reliability beats reflexes. But if you've run fast loops on a Cerbo without tears, or been burned by HA restarts at 2am, I'd like to hear about it.
I plan to build slowly and in the open. First, just watch: log frequency at one-second resolution for a few weeks, alongside state of charge, power and prices, and see what the midnight dip actually looks like from my meter point. Then shadow mode: run the full controller with no authority, logging what it would have done, and replay 23 June through it. Then live overnight charging only — bill-neutral, fast feedback. The emergency tier gets armed last. The controller itself stays deliberately boring: a small state machine, every decision logged with its inputs, no optimisation frameworks. Two clean seams — an inverter driver and a tariff driver — so someone with a Sungrow and Octopus can lift the middle out unchanged.
What I'm asking you
Thresholds and gains. Deadband ±0.015 Hz, full response by ±0.2 Hz — roughly 14–17 kW/Hz for my kit. Sensible? Too twitchy? Would you shape it like Dynamic Containment's knee instead? And where does the emergency discharge trigger belong, given 23 June never went below 49.657 — is 49.7 right, or does firing 2.8 kW into a 49.7 Hz evening a few times a year want something more graduated?
Does any of this actually help? Is household turn-down and surplus export visible and useful to the system operator, or does it vanish into the distribution noise? Anyone with a view of how NESO sees the midnight step, I'm all ears.
Victron war stories. Has anyone driven an ESS setpoint externally, once a second, for months? Register quirks, setpoint timeouts, ESS mode switching — gold dust.
Kraken slots. I believe E.ON Next exposes the same GraphQL family as Octopus's intelligent dispatches. Anyone consuming it in Home Assistant? How often do slots get revised mid-flight?
The loop question above — Cerbo or Home Assistant.
The FIT question, asked honestly. The daytime economics lean on my ~8p export payment — but those exported electrons would be off-peak grid charge, not solar, passing through a FIT export meter. Is that within the rules for storage co-located with FIT generation, or does deliberate export of grid-charged energy put the accreditation at risk? I'd rather learn it here than from a compliance letter. (If it's not allowed, the daytime response becomes throttle-to-zero-import only — still useful, half the swing.)
What am I missing? Battery wear from faithful frequency-following? A tariff-terms wrinkle in deliberately modulating consumption against frequency? The failure mode I haven't imagined at 3am?
For calibration, the kit: Victron Multiplus-II with a Cerbo GX, about 20 kWh of battery, 3.5 kW max charge, 2.8 kW max discharge, solar PV on a legacy FIT paying ~8p/kWh for export, an OpenEVSE charger, Home Assistant, and E.ON Next with a midnight-to-six window at 2.99p plus occasional extra cheap slots dispatched by Kraken.
Nothing is built. That's the point. Tell me why this won't work.




The key prize is whether NESO is able to call upon DER to maintain frequency. How is this to be achieved? Or if NESO cannot access it directly, how are DER able to maintain frequency? That's what you need to get onto once you've demonstrated that it is possible to use non-BM DER for frequency response