Peak-Shaving the Belgian Kwartierpiek with Marstek Batteries and Home Assistant
Belgium has a peculiar way of billing electricity. Alongside the energy you actually use, the grid charges you for the peak power you draw — specifically the highest 15-minute rolling average over each month, averaged again across the year. It’s called the capaciteitstarief, and the peak it keys on is the kwartierpiek. Get that number down and your bill comes down with it, regardless of how much energy you consume.
I have 13 kWp of solar on the roof and three Marstek Venus E batteries in the garage. The obvious play is to use the batteries to shave the peaks — discharge them when the house surges, so the grid never sees the spike. The non-obvious part is doing it well: keeping the battery reserve for the night, not spending it on every little sub-minute blip, never draining it below a safe floor, and — the part nobody warns you about — not letting your own overnight battery charging set a bigger peak than the one you spent all day avoiding.
This is the story of getting all of that working in Home Assistant, and the one nasty bug that quietly disabled the whole thing on the first evening.
The Belgian kwartierpiek, in one minute#
Under the capacity tariff, the digital meter records your average power over every clock quarter-hour (the energy drawn in those 15 minutes ÷ 0.25 h). That’s 96 values a day. Because it’s an average, a brief spike — the kettle, the heat pump starting — is diluted inside its 15-minute window. What hurts is sustained high draw: charging an EV for 20 minutes, or the oven and the dishwasher and the dryer all running across the same quarter.
Three details matter for sizing a battery system against it:
- It’s summed across all three phases. A battery injecting on one phase offsets a load on another, for billing purposes. The meter nets them.
- Your monthly peak (maandpiek) is the single highest quarter of the month, and there’s a 2.5 kW floor — you pay for at least 2.5 kW whether you hit it or. Below 2.5 kW, there is nothing to win.
- What you actually pay is a 12-month rolling average of those monthly peaks. One bad month is diluted to a twelfth of its impact — which takes the urgency out of any single breach, but also means every month still counts.
My June peak was already locked at 2.707 kW before I started, so the controller can only protect the months from July onwards. The target is to hold future monthly peaks at or under 2.5 kW.
The hardware#
- 3× Marstek Venus E 3.0 batteries, 5.12 kWh each — 15.36 kWh total — one per phase.
- ≈13.1 kWp of solar across two inverters (one of them the Platinum 3100S I wrote about recently ).
- A Slimmelezer P1 dongle reading the Fluvius digital meter, giving me
sensor.power_combined(net grid power, updated about once a second), plus the livekwartierpieksensors. - The batteries expose native Modbus TCP on port 502 — no separate bridge needed.
One hard limit up front: the batteries are temporarily plugged into ordinary wall sockets, so discharge is software-capped at 800 W each — about 2.4 kW total across the three. That’s enough to shave most household peaks, but a big evening cook plus everything running can still blow through it. Moving them onto dedicated 16 A circuits (2.5 kW each, 7.5 kW total) is the single biggest improvement still ahead.
Why the ffunes Marstek Venus Energy Manager#
There are a few Home Assistant integrations for these batteries, but most are just readers — they show you state of charge and power. I needed one that writes setpoints and actually orchestrates the batteries. The clear choice was ffunes’ Marstek Venus Energy Manager (MVEM) . It talks native Modbus TCP to up to six batteries, exposes power setpoints, and — usefully for me — the author added its Capacity Protection feature specifically for another Flanders user wrestling with the same kwartierpiek.
The whole thing is configured through a guided UI flow, no YAML, and crucially for me: no Node-RED. Every bit of control is MVEM plus native Home Assistant automations. MVEM replaces the read-only Marstek Modbus integration I had before (left installed but disabled, so the Modbus socket is free).
The four dials that do the work#
MVEM gives me four features that map directly onto the goals, and getting their relationship right is the whole game:
PD zero-export keeps the grid meter at ~0 W whenever there’s battery to spend — the house runs off solar and battery, exporting nothing and importing nothing. This is self-consumption.
Maximum contracted power caps how much the batteries charge from the grid. Set it to 2.5 kW and the three batteries can never pull more than that combined, no matter the mode. This is the one that stops the overnight top-up from becoming its own 7.5 kW peak.
Capacity Protection (which is just MVEM’s name for peak shaving) is the
discharge-side cap. When the battery is below a state-of-charge threshold, it
only discharges for the part of a load that crosses an instantaneous limit —
battery = max(0, grid − limit). So a 3 kW surge with a 2 kW limit gets 2 kW
from the grid and 1 kW from the battery. It’s instantaneous, not a 15-minute
average, which makes it a conservative proxy: holding the instantaneous draw
under a value guarantees the 15-minute average stays under it too. It “spends”
a little battery on short spikes that wouldn’t actually have moved the average,
but at my 2.4 kW discharge cap that’s a non-issue today.
Predictive charging tops the batteries up from the grid overnight (01:00–05:00, the cheap window) using tomorrow’s solar forecast, so the house wakes up with enough reserve to run until dawn on solar — and that charge is itself bounded by the Maximum contracted power above.
The thresholds I settled on, conservatively: Capacity Protection limit 2.0 kW, SoC threshold 35%, minimum SoC floor 12% (MVEM v2.0.4 enforces a 12% minimum), Maximum contracted power 2.5 kW. Tune after a few weeks of real data.
The trap I almost didn’t notice#
On the first evening, the sun was down, the batteries were at 97%, the house was
drawing a modest 600 W — and the grid was supplying 500 W of it while all three
batteries sat idle in standby. The system status read battery_limited, “no
available batteries.” Something was blocking discharge entirely.
The culprit was my own EV. I’d added the Skoda Enyaq to MVEM’s Load Exclusion
list — the set of devices the battery should never power — using its charge
state from the MySkoda integration, because MySkoda’s charge-power reading is
flaky. MVEM decides an EV is charging by substring-matching the state text for
"charg". MySkoda’s state for parked with the cable plugged in but not
charging is ready_for_charging. Which contains charg. So MVEM concluded the
car was charging, and — correctly, for a real charge — blocked all battery
discharge so the house battery would never feed the car. Every evening the cable
was plugged in, the system went dead.
It was a genuine false positive that recurred nightly and would have quietly
killed the entire night-coverage goal. The fix was to stop trusting the state
string and gate on real charge power instead. A tiny template sensor returns
charging only when the car is actually pulling more than 0.2 kW; otherwise
idle. Point MVEM’s excluded device at that, restart, and the false block
disappears — real charges still arm it, plugged-in idle no longer does.
The lesson generalises: when an integration lets you feed it a state instead of a measurement, prefer the measurement. State machines carry words like “ready” and “waiting” that look innocent until a substring match somewhere upstream decides “charg” means “charging.”
The other trap: a feature that does the opposite of its name#
The second surprise was MVEM’s Solar Charge Delay. Sounds ideal — postpone charging until solar will cover it. I turned it on, watched a sunny morning, and the batteries sat half-empty while the inverter exported solar to the grid. The feature is a blanket charge-blocker: when active it blocks all charging, including the zero-export solar-surplus charging that’s the entire point. With a healthy forecast it cheerfully computed it could wait until 3:25 PM to start — by which time it had given away the whole morning’s surplus. I turned it off and charging resumed within the minute.
In a zero-export setup there is no morning grid-charge to prevent, so the delay has nothing useful to do and only gets in the way. Off it stays.
Where it stands now#
Phase 1 is complete and live: all three batteries orchestrated, zero-export self-consumption working, capacity protection and predictive night charging engaged, the Enyaq excluded and the false-block fixed. It’s been soaking since, collecting trustworthy data.
What’s not done yet — the next phases — is the comfort loads. The policy is firm: the air conditioning and the water heater may run only on real solar surplus (the grid actively exporting), never off the battery. That needs a power sensor on each AC feed and ESPHome relays on the water-heater elements, then native HA automations to stage them on and off as surplus rises and falls, with compressor-safe hysteresis so the ACs aren’t short-cycled. That’s the next chunk of work; the battery brain underneath it is already running.
Lessons learned#
Instantaneous control is a safe approximation of the kwartierpiek, not a precise one. Shaving the instantaneous draw to 2 kW guarantees the 15-minute average stays under 2 kW, but it spends battery on spikes a true quarter-budget controller would have ignored. Worth reclaiming that efficiency later — but only after the rewiring unlocks real discharge headroom. Until then the cap is the battery, not the algorithm.
The charging side is as dangerous as the discharging side. Three batteries charging at 2.5 kW each is a 7.5 kW import that would demolish the monthly peak at 3 AM. The “Maximum contracted power” cap on charging is what makes night top-up safe, and it has to apply in every mode — predictive, weekly full charge, all of them.
A 2.5 kW floor means a 2.0 kW target buys margin, not money. The capacity tariff bills a minimum 2.5 kW no matter what, so driving the average below 2.5 yields zero euros. The value of a 2.0 kW setpoint is purely safety margin against averaging lag and overshoot. Once the behaviour is understood, 2.3–2.4 is probably the sweet spot — margin without needless battery wear.
Beware substring matching on foreign state machines. The single bug that silently disabled the system was a three-letter substring colliding with a word in somebody else’s status string. Prefer measurements over states wherever you can.
Resources#
- ffunes Marstek Venus Energy Manager (MVEM) — the integration doing the orchestration
- MVEM documentation — feature reference, including Capacity Protection
- MVEM forum thread — including the Flanders peak-shaving discussion and PID tuning
- Belgian kwartierpiek in Home Assistant — computing the 15-minute peak from a P1 meter
- Home Assistant — open source home automation
- Bringing a second-hand Solar-Log 500 online — the inverter monitoring feeding this same energy setup