Capaciteitstarief beperken met Marstek batterij(en).

Pagina: 1
Acties:

  • GORby
  • Registratie: Januari 2002
  • Laatst online: 09:04

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: 09:04
placeholder

  • ranico
  • Registratie: Oktober 2025
  • Laatst online: 09-02 20:13

ranico

TL4000-TL5000

Leuk om dit nu in een apart topic te zetten.
Ik heb momenteel de eenvoudige versie zowat een maand ik gebruik, en al zeer tevreden over.
De modbus optimalisatie is voor de nabije toekomst.

Belgie - 3Fase - 3xMT VenusE g3 V147 - CT003 v118 - 39 Panelen 9945Wp


  • GORby
  • Registratie: Januari 2002
  • Laatst online: 09:04
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.

  • ranico
  • Registratie: Oktober 2025
  • Laatst online: 09-02 20:13

ranico

TL4000-TL5000

Knap, dat is al een serieuze upgrade.
In dit geval heb je geen CT0002/3 meer nodig? En is alles gestuurd met modbus en HA.

Belgie - 3Fase - 3xMT VenusE g3 V147 - CT003 v118 - 39 Panelen 9945Wp


  • GORby
  • Registratie: Januari 2002
  • Laatst online: 09:04
Wel, de CT003 wordt nog gebruikt om Marstek 2 aan te sturen, hoewel dat prima zou kunnen met de HW P1 meter normaal gezien. Marstek 2 stuurt zowel in peak shaving als in self-consumption altijd naar 0 op de echte meter, maar self-consumption stopt er met deze settings mee bij 27%, en 15% van de batterij wordt dan gehouden voor peak shaving.

Marstek 1 wordt door de virtuele meter gestuurd, en die krijgt bij peak shaving een waarde met offset, maar bij solar charging of self-consumption krijgt die de echte waarden. Eens ik tevreden ben van die config, zal ik de uitbreiding er hier nog bij zetten.

Modbus wordt enkel gebruikt om het laad- en ontlaadvermogen per batterij te beperken, om de efficiëntie wat te verhogen en om ervoor te zorgen dat de ene batterij de andere niet gaat opladen.

  • SatScan
  • Registratie: Februari 2024
  • Laatst online: 09:35
Mooi werk zover !

Ik wil twee tips meegeven...
a) vul je signatuur dan weten we met welke configuratie je test/werkt
b) als je code post is de qoute tag handig, dan wordt het ingeklapt en blijft het topic mi beter leesbaar

.NL | BYD Atto3 | PulsarPlus EV +Balancer | WP7.7K Z | Venus v1 en v2 - 5.12KWh V157.3 - CT003 V122 - BMS 216 - Modi:NOM | 2 MHI - CC | HA DS224+


  • GORby
  • Registratie: Januari 2002
  • Laatst online: 09:04
SatScan schreef op maandag 19 januari 2026 @ 09:30:
Mooi werk zover !

Ik wil twee tips meegeven...
a) vul je signatuur dan weten we met welke configuratie je test/werkt
b) als je code post is de qoute tag handig, dan wordt het ingeklapt en blijft het topic mi beter leesbaar
Bedankt voor de tips. Die signature, kan je dat per topic instellen? Anders vind ik het geen interessante optie, want dan komt die info onder all mijn posts op alle topics waar ik actief ben. Da's nogal een rotzooi dan .
Pagina: 1