Hi all, DESS heeft bij mij wat edge cases. Ik heb een contract bij Tibber.
De standaard formule inclusief belastingen en overige kosten zorgt er voor dat DESS overtollige PV verkoopt zelfs wanneer de prijs exclusief onder de 0 cent zit. Pas wanneer de prijs inclusief belastingen en overige kosten onder de 0 zakt stopt DESS met exporteren. Dit is problematisch wanneer je meer PV exporteert dan je via de saldering terug mag pakken. En daarnaast is een prijs onder de 0 een signaal dat er teveel stroom is, dus is het beter voor het net om op dat moment niet / weinig te exporteren.
Anderzijds, de formule zonder belastingen maar met transport kosten is erg snel negatief en zorgt er voor dat DESS energie in gaat kopen van het net zelfs wanneer er die dag voldoende PV is. Vaak is de prijs inclusief belastingen en overige kosten dan alsnog positief.
Idealiter zou je in DESS een soort minimale in/verkoop prijs in kunnen stellen. Maar naar mijn weten kan dit momenteel niet.
Wat wel kan is een formule bouwen die deze edge cases afvangt.
Voor de goede orde, dit is het gewenste gedrag in python code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
| # gewenste buy formule
def buy(price):
taxed_price = calc_taxed_price(price)
if taxed_price < 0:
return price
return taxed_price
# gewenste sell formule
def sell(price):
if price <= 0:
return price - 0.02
return calc_taxed_price(price) - 0.02 |
Dit geeft de volgende impulse response bij een prijs tussen de [-1, 1].
Je ziet hier op de x-as de prijs / kWh en op de y-as het resultaat uit de gewenste formule. Buy = buy formula in DESS, sell = sell formula in DESS. Met dit gedrag stopt DESS met verkopen wanneer de prijs exclusief belastingen onder de 0 komt (met een kleine bias van -0.02 zodat DESS niet te snel verkoopt) en gaat DESS ook niet te snel opladen van het net omdat voor de buy de prijs inclusief belastingen aangehouden wordt totdat deze onder de 0 komt. Dit neemt de vorm aan van een wybertje.
Nu zit er in DESS een abs() functie. Abs() doet niets als een getal positief is, maar als een getal negatief is maakt dit een positief getal van het negatieve getal. Bijv. abs(-1) = 1, abs(1) = 1. Daar kunnen we een if-else mee emuleren.
code:
1
2
3
4
| abs(x) + x = 0 wanneer x negatief is, ze heffen elkaar op
abs(x) + x = 2*x wanneer x positief is.
(abs(x) + x) / (2*abs(x)) = 0 wanneer x negatief is of 1 wanneer het getal positief is. |
Dit kunnen we gebruiken om bepaalde stukken formule aan of uit te zetten op basis van deze 0 of 1. Je krijgt dan best een wilde buy / sell formule. In dit geval specifiek voor Tibber, met een kleine bias om division by zero edge cases tegen te gaan, en in het geval van sell ook wat bias om niet te snel te verkopen. Het systeem is immers niet 100% efficient.
code:
1
2
3
| buy = ((p + 0.0175 + 0.1088) * 1.21 * (abs(p + 0.0175 + 0.1088) + (p + 0.0175 + 0.1088)) / (2 * abs(p + 0.0175 + 0.1088))) + p * (abs(p + 0.0175 + 0.1088) - (p + 0.0175 + 0.1088)) / (2 * abs(p + 0.0175 + 0.1088))
sell = ((((p + 0.0175 + 0.1088) * 1.21) * (abs(p) + p) / (2 * (abs(p) + 0.00001))) + (p) * (abs(p) - p) / (2 * (abs(p) + 0.00001))) - 0.02 |
Je ziet in VRM dan een flinke pulldown ontstaan wanneer de sell prijs exclusief belastingen maar met transport kosten onder de 0 zakt. DESS reguleert in dat geval mijn MPPTs zodat er bijna niets naar het net geëxporteerd wordt.
De formule is voor onze pythonistas makkelijk te updaten via de VRM api:
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
| from copy import deepcopy
import requests
import json
access_token = "<secret>"
id_site = '<secret>'
def get_ess_config(id_site, access_token):
headers = {
'x-authorization': f'Token {access_token}'
}
url = f"https://vrmapi.victronenergy.com/v2/installations/{id_site}/dynamic-ess-settings"
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
def update_ess_settings(id_site, access_token, data):
headers = {
'x-authorization': f'Token {access_token}',
'Content-Type': 'application/json'
}
url = f"https://vrmapi.victronenergy.com/v2/installations/{id_site}/dynamic-ess-settings"
response = requests.patch(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
return response.json()
# Retrieve ESS buy and sell formulas
ess_config = get_ess_config(id_site, access_token)
print('ESS Config:')
print(json.dumps(ess_config, indent=2))
mod_ess_config = {}
mod_buy = '((p + 0.0175 + 0.1088) * 1.21 * (abs(p + 0.0175 + 0.1088) + (p + 0.0175 + 0.1088)) / (2 * abs(p + 0.0175 + 0.1088))) + p * (abs(p + 0.0175 + 0.1088) - (p + 0.0175 + 0.1088)) / (2 * abs(p + 0.0175 + 0.1088))'
mod_sell = '((((p + 0.0175 + 0.1088) * 1.21) * (abs(p) + p) / (2 * (abs(p) + 0.00001))) + (p) * (abs(p) - p) / (2 * (abs(p) + 0.00001))) - 0.02'
# sanitize formula (remove whitespaces)
mod_buy = mod_buy.replace(' ', '')
mod_sell = mod_sell.replace(' ', '')
mod_ess_config['buyPriceFormula'] = mod_buy
mod_ess_config['sellPriceFormula'] = mod_sell
update_ess_settings(id_site, access_token, mod_ess_config) |
Edit 2025-07-03; Kleine bugfix in de sell formule