Capaciteitstarief beperken met Marstek batterij(en).

Pagina: 1
Acties:

  • GORby
  • Registratie: Januari 2002
  • Laatst online: 02-04 14:16

Waarom dit topic?

Aangezien ik van een paar mensen al wat vragen heb over de Marstek sturing die ik in Home Assistant opgezet heb om het capaciteitstarief wat onder controle te houden, maak ik er maar even een apart topic van.

Doel van deze sturing

De voornaamste sturing is die van verbruikers. Door het verbruik te spreiden, voorkom je de grootste pieken. Bepaalde verbruikers kan je echter niet of moeilijk spreiden, en je kan ook maar beperkt dingen uitschakelen wanneer er een grote piek aankomt. Die momenten kunnen dan opgevangen worden door de batterij.

De implementaties van peak shaving die ik tot nu toe gezien had, laten toe dat je een limiet instelt, en van het moment dat het vermogen daarboven komt, gaat de batterij de overschot bijpassen, zodat er een harde limiet is op het vermogen dat er van het net genomen wordt.

Voor de typisch Belgische situatie met het capaciteitstarief, waarbij het gemiddeld opgenomen vermogen van elk kwartier berekend wordt, en de hoogste kwartierpiek van elke maand meetelt voor een stuk van je netvergoeding, is dat niet zo interessant, want je gaat de batterij zo veel meer gebruiken dan nodig is om je kwartierpiek te beperken, waardoor je verliezen (door de round trip efficiëncy van de batterij) groter zijn, en het misschien niet meer rendabel is.

Ik wilde een sturing die een tijdelijke piekbelasting niet gaat corrigeren, wanneer er nog marge is. Om te bepalen wat die marge is, kijk ik in de eerste 10 minuten van elk kwartier naar het gemiddelde vermogen over de laatste 10 en 15 minuten.
De laatste 5 minuten wordt er effectief gestuurd op het vermogen dat dat kwartier nog gebruikt kan worden zonder over de limiet te gaan. Hiervoor wordt het aantal Watt-seconden dat nog overblijft gedeeld door het aantal seconden dat resteert in het kwartier, rekening houdend met wat marge om de traagheid van de batterij en clock drift van de digitale meter te compenseren.

Componenten en configuratie basis sturing

Minimale benodigdheden
  • Home Assistant
  • Digitale meter met actieve P1 poort en een module om die uit te lezen, ofwel een andere vermogenmeter die je kan uitlezen
  • B2500 Home Assistant add-on (https://github.com/tomquist/b2500-meter)
  • Marstek plug-in batterij. De B2500 add-in is enkel bedoeld om deze batterijen voor de gek te houden.
Optionele benodigdheden
Voor een betere sturing, willen we ook het maximum laad- en ontlaadvermogen sturen afhankelijk van de omstandigheden. Hiervoor is de modbus koppeling nodig, ofwel op de Venus V3 via de modbus over TCP (enkel via ethernet), ofwel via een modbus module die verbonden is met de RS485 poort.
Hiervoor gebruik ik https://my.home-assistant...tory=marstek_venus_modbus
Home assistant helpers
statistieken en parameters
Omdat we bij de kwartierpiekbeperking de batterij niet naar 0W op de meter willen sturen, maar naar een waarde die een variabele offset heeft ten opzichte van de echte meter, hebben we een paar helpers nodig.
  • sensor.p1_10_minute_average_power: statistics helper met het lopend gemiddelde over 10 minuten van de p1 meter
  • sensor.p1_5_minute_average_power: statistics helper met het lopend gemiddelde over 5 minuten van de p1 meter
  • input_number.grid_power_limit_target: slider waarmee je je gewenste grens voor de kwartierpiek instelt
berekening variabele vermogensgrens
We willen een grens bepalen waarboven de batterij moet gaan ingrijpen. Deze grens hangt natuurlijk af van de ingestelde grens, maar om wat headroom te hebben, wordt er de eerste 10 minuten van het kwartier gekeken naar het gemiddelde van de laatste 5 en 10 minuiten. Later in het kwartier weten we wat nauwkeuriger hoeveel vermogen er de rest van het kwartier nog mag gebruikt worden om niet over de piek te gaan, dus voor de laatste 5 minuten wordt die waarde gebruikt.

variabele offset: number.grid_power_limit_target_adjusted
Dit is een template helper, die ik in de instellingen begrensd heb tussen 0 en 22.000, maar die nog een eigen begrenzing krijgt, gebaseerd op de ingestelde limiet.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
{# -----------------------------
  Inputs 
------------------------------ #}
{% set setTarget = states('input_number.grid_power_limit_target') | float(0) %}
{% set quarterAvg = states('sensor.p1_meter_average_demand') | float(0) %}
{% set avg10m = states('sensor.p1_10_minute_average_power') | float(0) %}
{% set avg15m = states('sensor.p1_15_minute_average_power') | float(0) %}

{# -----------------------------
  Parameters
------------------------------ #}
{% set latency_s     = 5 %}          {# Latency of the battery #}
{% set quarterBand_W = 100 %}        {# Margin in W for the quarter-hour average. Make sure we end up below the limit #}
{% set avgBand_W     = 0 %}          {# Margin in W for the averages. Optional since we steer towards the effective remaining power budget #}
{% set k10_up        = 3.50 %}       {# How quickly the target power will rise based on the 10 minute average #}
{% set k10_down      = 1.00 %}       {# How quickly the target power will drop based on the 10 minute average #}
{% set quarterStartGuard_s = 2 %}    {# Prevent strange readings around quarter-hour average reset. 5s should be enough #}
{% set p1MeterDrift_s = 4 %}         {# How many seconds after the quarter-hour the p1 meter resets the average demand #}
{% set downSlopeTarget_s = 60 %}     {# Number of seconds before the end of the quarter where the target adjusts down #}
{% set downSlopeSpeed_W_s = 0.5 %}   {# Number of watts to lower the target by every second into the downslope target #}

{# -----------------------------
  Time remaining in current quarter-hour, try to compensate for p1 meter clock drift up to 60s
------------------------------ #}
{% set secIntoQuarter = (now().minute % 15) * 60 + now().second %}
{% if secIntoQuarter <= 60 and quarterAvg > (setTarget / 2) %}
  {% set secIntoQuarter = 900 %}
{% endif %}
{% set secLeft  = 900 - secIntoQuarter %}
{# effective time left after accounting for battery latency #}
{% set secLeftEff = [1, p1MeterDrift_s, secLeft + p1MeterDrift_s - latency_s] | max %}
{% if secIntoQuarter < quarterStartGuard_s %}
  {% set quarterAvgEff = 0 %}
{% else %}
  {% set quarterAvgEff = quarterAvg %}
{% endif %}


{# Gradually aim for a lower target the last minute, but don't go lower than quarterAvg + 10. Hard limit to setTarget - 5 #}
{% if secIntoQuarter >= (900 - downSlopeTarget_s) %}
  {% set t = secIntoQuarter - (900 - downSlopeTarget_s) %}     {# seconds into the down-slope window: 0..downSlopeTarget_s #}
  {% set adjTarget = setTarget - (downSlopeSpeed_W_s * t) %}
  {% set adjTarget = [adjTarget, quarterAvg + 10] | max %}
  {% set adjTarget = [adjTarget, setTarget- 5] | min %}
{% else %}
  {% set adjTarget = setTarget %}
{% endif %}
{# -----------------------------
  Quarter-hour constraint (hard limit)
  quarterAvg is "average if the rest of the quarter-hour would be zero consumption".
  That implies energy already used this quarter ≈ quarterAvg * 900 (W*s).

  To finish the quarter at adjTarget, the max constant power for the remainder is:
    p <= (adjTarget*900 - energy_so_far) / seconds_left, compensated for latency
------------------------------ #}
{% set energySoFar = quarterAvgEff * 900 %}
{% set powerBudgetInQuarter = ((adjTarget * 900) - energySoFar) / secLeftEff %}

{# -----------------------------
  10-minute constraint (soft limit)
  - If avg10m is comfortably below target, allow some headroom above adjTarget.
  - If avg10m is near/over target, pull below adjTarget more aggressively.
------------------------------ #}
{% set diff10 = adjTarget - (avg10m + avgBand_W) %}
{% if diff10 >= 0 %}
  {% set p10 = adjTarget + diff10 * k10_up %}
{% else %}
  {% set p10 = adjTarget + diff10 * k10_down %}
{% endif %}

{# -----------------------------
  Combine:
  - The first 10 minutes, base the target on the calculation with the 10 minute power, unless the power budget is higher, or 15 minute average close to setTarget
  - Next, follow the power budget
------------------------------ #}
{% set p = [p10, 2*adjTarget] | min %}
{% set p = [p, powerBudgetInQuarter] | max %}
{% if secIntoQuarter >= 600 or (avg15m - setTarget) | abs < 50 %}
  {% set p =  powerBudgetInQuarter %}
{% endif %}

{# Limit p to 2*adjTarget, and round to nearest integer #}
{% set p = [p, 2*adjTarget] | min %}
{% set p = [0, p] | max %}

{{ p | round(1) }}
Waarde gesimuleerde p1 meter
De offset die in de vorige helper berekend is, wordt gebruikt om een waarde voor de gesimuleerde p1 meter te berekenen. Deze helper gebruik je als input voor de B2500 add-in.
code:
1
2
3
4
5
6
7
8
{#---------------------
Inputs 
-----------------------#}
{% set adjTarget = states('number.grid_power_limit_target_adjusted') | float %}
{% set pInstant = states('sensor.p1_meter_power') | float %}

{% set p = [pInstant, p10s] | max %}
{{ pInstant - adjTarget }}


Eens je dit toegevoegd hebt, en deze laatse waarde als input voor de B2500 add-in ingesteld hebt, kan je de Marstek koppelen aan de gesimuleerde meter en zal die je piek gaan beperken. Dit is dan het resultaat:
Afbeeldingslocatie: https://tweakers.net/i/rlr1b3N7801KQu04O9RXxNljyls=/800x/filters:strip_exif()/f/image/AACSpKEqASUN2QBCTzyvznps.png?f=fotoalbum_large

Uitbreiding

Tekortkoming van de eenvoudige versie
De sturing zoals die hierboven staat werkt om de kwartierpiek te beperken, maar omdat de batterij zichzelf ook onmiddellijk wil opladen wanneer het vermogen wat zakt, krijg je bij sommige belastingen een heel zenuwachtige sturing. Bovendien zorgt dit ervoor dat er een stuk meer vermogen gebruikt wordt dan wanneer de batterij niet zou gaan laden, waardoor de batterij later in het kwartier weer vermogen moet gaan leveren om zijn eigen verbruik van net daarvoor te gaan compenseren.
Minimale aanpassing
Een eenvoudige minimale aanpassing, is het laadvermogen via de Modbus integratie te beperken tot een lage waarde, bijvoorbeeld 200W. Hierdoor zal de batterij wel gaan laden, maar wordt het vermogen niet de hele tijd tot tegen de limiet getrokken tot de batterij vol is. Aangezien het mijn doel is om de gebruikte capaciteit voor peak shaving te beperken tot pakweg 1kWh per dag, met hier en daar eens een uitschieter naar 2kWh of zo, is een trage laadsnelheid prima. Daarmee krijg je dan bijvoorbeeld dit:
Afbeeldingslocatie: https://tweakers.net/i/DYbqBRAXVbshWtsi8oKL6wQVhu8=/800x/filters:strip_exif()/f/image/zyqPywoHPUJt0wXNgYYW3Bqt.png?f=fotoalbum_large
betere oplossing
Om dit iets beter te regelen, kan je met een paar automations werken om het laden uit te stellen tot het verbruik een stuk lager ligt:
  • eentje die het maximum laadvermogen op 0 zet zodra de batterij moet terugleveren
  • een andere die het maximum laadvermogen instelt op bijvoorbeeld 500 of 1000W, wanneer het verbruik ver genoeg gezakt is. Hierdoor wordt er wat efficiënter geladen dan aan 200W.
  • een laatste die het maximum laadvermogen aanpast op basis van de SOC van de batterij. Wanneer die bijna leeg is, wil je waarschijnlijk sneller laden of dadelijk gaan laden.
Hiermee krijg je dan een volgend beeld. De batterij levert terug, maar het laden gebeurt pas wanneer het verbruik wat zakt, en duurt dan ook ruim 2 uur. Hier werd er nog maar geladen met 200W.
Afbeeldingslocatie: https://tweakers.net/i/8hsLLnuVGXm8RtOiumJZCojWhXw=/800x/filters:strip_exif()/f/image/YN5VEUqtbVQXzwNAVnTpf3mC.png?f=fotoalbum_large

[ Voor 0% gewijzigd door GORby op 19-01-2026 11:02 . Reden: groot code blok in quote gezet ]


  • GORby
  • Registratie: Januari 2002
  • Laatst online: 02-04 14:16
Ondertussen ben ik zover dat beide batterijen aangestuurd worden, zodat ik de overtollige productie van de panelen binnenkort kwijt kan in beide batterijen. Ook hier wordt het weer een sturing van verbruikers en batterijen, zodat zoveel mogelijk rechtstreeks opgebruikt wordt, en de rest in de batterijen gaat.
Afbeeldingslocatie: https://tweakers.net/i/ENQKrld8v9t_0QcuZbAlPOgXK_I=/x800/filters:strip_exif()/f/image/zkwfQHexdCM8PoqJ9sfGYdUU.png?f=fotoalbum_large
Marstek 1 wordt altijd opgeladen tot een bepaald minimum percentage, om een buffer te hebben voor peak shaving. Marstek 2 wordt primair gebruikt voor solar charging, wat bij mij wil zeggen dat er enkel geladen, maar nog niet ontladen wordt. Dit heeft 2 effecten:
  1. er wordt iets minder teruggestuurd bij wisselende belasting
  2. wanneer de batterij wel begint terug te sturen, zal het om grotere vermogens gaan en dus met een betere efficiëntie
Eens die batterij bijna vol is, gaat die naar self-consumption en gaat die dus NOM draaien (met max 800W ontladen). Wanneer die volledig vol is, wordt Marstek 1 op solar charging gezet, zodat die de rest van het overschot op kan slaan. Wanneer er bijgestuurd moet worden, schakelen beide naar peak shaving, waardoor Marstek 2 naar 0W wil sturen, en dus 800W teruglevert zo lang de vraag groter is dan 800W, en Marstek 1 pas ingrijpt wanneer het vermogen alsnog de kwartierpiek in gevaar brengt.

  • GORby
  • Registratie: Januari 2002
  • Laatst online: 02-04 14:16
Bedankt, dat ziet er op zich mooi uit, maar gaat blijkbaar wel het vermogen begrenzen tot de ingestelde piek. Met mijn sturing bereik ik hetzelfde, maar dan met veel minder gebruik van de accu, doordat het hogere vermogen enkel gecompenseerd wordt door de batterij wanneer er in dat kwartier geen ruimte is voor extra verbruik.
Om een heel extreem voorbeeld te geven: Als ik de kwartierpiek wil begrenzen tot 2500W een sluimerverbruik heb van 200W en ik de eerste 10 minuten van het kwartier geen extra verbruikers aan heb, dan heb ik 33Wh verbruikt. Dat wil zeggen dat ik de laatste 5 minuten van het kwartier nog 592Wh mag verbruiken, dus dat ik ruim 7kW mag trekken voor mijn kwartierpiek in de buurt van de grens komt.

Deze sturing zal dan 5 minuten lang 4,5kW willen compenseren en trekt 4,5kW/12 = 375Wh uit de batterij. Resulterende kwartierpiek: ~ 1kW, dus er is te veel gecompenseerd.
Mijn sturing laat de batterij rustig niets doen, omdat er geen nood is om iets te doen. Als er 8kW gevraagd zou worden, zou mijn batterij wel beginnen terugleveren, maar dan net genoeg om het gemiddelde vermogen over het kwartier tot 2,5kW te begrenzen.

Een voorbeeld van vandaag zie je hier in deze grafiek. Mijn limiet voor deze maand staat ingesteld op 4200W. We hebben tegen het eind van het kwartier even een verbruik van 6,3kW, zonder dat de batterij ingrijpt. De kwartierpiek blijft nog onder de 4kW.
Afbeeldingslocatie: https://tweakers.net/i/009SuC2f1DAxs-jo08k3oJGutX8=/800x/filters:strip_exif()/f/image/oZuslG1YzePJn3ic5of5RpHH.png?f=fotoalbum_large

Hier is een voorbeeld waar het vermogen al even hoog was, dus we zaten al kort tegen de limiet. De korte piek in het begin van het kwartier wordt deels gecompenseerd, en even later begint de batterij de rest van het kwartier te compenseren. In dit kwartier zou het verschil tussen beide sturingen klein geweest zijn. De kwartierpiek komt uit op 4187W.
Afbeeldingslocatie: https://tweakers.net/i/i4Lxrtdx2oJfQC35fs9bRZs8ZlE=/800x/filters:strip_exif()/f/image/KBBMrowXljDkO6eR8RI30fKS.png?f=fotoalbum_large

Hier zitten we tijdens het koken met een zeer sterk wisselende belasting. Aangezien we hier met een vrij hoog gemiddeld vermogen beginnen te zitten, is de grenswaarde voor het ingrijpen van de batterij al wat gezakt, maar grijpt de batterij toch niet in elke keer het vermogen boven de 4200W komt.
Afbeeldingslocatie: https://tweakers.net/i/hICJQzcXVn9Bv-vr8XimBQeHsNc=/800x/filters:strip_exif()/f/image/YtNdvZ01T8bfTqvPsJeos3WG.png?f=fotoalbum_large

Het resultaat tot zover voor februari is dat de effectieve kwartierpiek op 4205W zit. Met de sturing van home battery control zou het lager dan 4200W geweest zijn, maar zou het ons meer gekost hebben aan omzettingsverliezen

[ Voor 3% gewijzigd door GORby op 22-02-2026 00:23 ]


  • GORby
  • Registratie: Januari 2002
  • Laatst online: 02-04 14:16
Je bedoelt dan die {% ... %} neem ik aan?
Dat is omdat dat een template helper of waarde is, waarvoor HA de Jinja2 syntax gebruikt. Daar wordt een statement (een lijn dus ook in mijn geval) zo begrensd.

Zo'n template helper maak je via settings - devices - helpers.
Daar klik je op "+ Create helper" en kies je uit de lijst "Template".
Dan moet je nog een type kiezen, en in mijn voorbeeld is dat "Number". Je komt dan op een scherm uit als in de onderstaande screenshot van mijn helper "number.p1_meter_power_offset", waar die code met de % thuis hoort.

Afbeeldingslocatie: https://tweakers.net/i/GoYxUjWG8U4UIf0sGqlcsh7szBg=/x800/filters:strip_exif()/f/image/DKjb45w6fCkmxV0ULnfaFweh.png?f=fotoalbum_large

edit: die input_select.marstek_1_mode en number.grid_power_limit_target_adjusted zijn op hun beurt weer andere helpers. Die marstek_1_mode is een drop-down waarmee ik de werking van de batterij kan wisselen tussen bijvoorbeeld peak shaving en self-consumption.
Die grid_power_limit_target_adjusted is eigenlijk de belangrijkste, die de offset berekent die toegepast wordt op de effectieve p1 waarde, om het vermogen uit het net niet naar de echte 0 te sturen, maar naar de waarde die ik wil. Dat is die lange blok jinja2 template die in mijn originele post staat.

[ Voor 24% gewijzigd door GORby op 04-03-2026 09:02 ]

Pagina: 1