Ik heb mbt tot DAO 2 grafieken gemaakt:
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
| type: custom:apexcharts-card
header:
show: true
title: Frank prijs + DAO batterij SoC + PV forecast
show_states: false
graph_span: 24h
span:
start: day
now:
show: true
label: Nu
yaxis:
- id: price
min: '|-1|'
max: '|+1|'
decimals: 0
opposite: false
apex_config:
offsetX: -45
tickAmount: 10
title:
text: PRIJS
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(0) + " ct";
}
- id: soc
min: 0
max: 100
decimals: 0
opposite: true
apex_config:
title:
text: SOC
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(0) + "%";
}
- id: zon
min: 0
decimals: 1
opposite: true
apex_config:
offsetX: 35
title:
text: ZON
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(1) + " kW";
}
series:
- entity: sensor.frank_prices_today
name: Frank prijs
yaxis_id: price
type: line
curve: stepline
color: '#00B7FF'
stroke_width: 2
opacity: 1
unit: ct/kWh
float_precision: 2
data_generator: |
return entity.attributes.prices.map(p => [
new Date(p.from).getTime(),
p.total_price_eur_kwh * 100
]);
show:
legend_value: false
- entity: sensor.dao_battery_soc
name: DAO SoC
yaxis_id: soc
type: line
curve: stepline
color: '#A0C770'
stroke_width: 2
opacity: 1
unit: '%'
float_precision: 0
data_generator: |
return entity.attributes.data.map(p => [
new Date(p.time).getTime(),
parseFloat(p.value)
]);
show:
legend_value: false
- entity: sensor.solcast_pv_forecast_voorspelling_vandaag
name: Zon voorspelling
type: line
yaxis_id: zon
color: '#FABF00'
opacity: 0.7
stroke_width: 2
curve: stepline
data_generator: |
const data = entity.attributes.detailedForecast;
const result = [];
data.forEach(p => {
const t = new Date(p.period_start).getTime();
const value = Number(p.pv_estimate);
result.push([t, value]);
result.push([t + 15 * 60 * 1000, value]);
});
return result;
show:
legend_value: false
apex_config:
chart:
height: 400px
tooltip:
shared: true
intersect: false
grid_options:
columns: full |
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
| type: custom:apexcharts-card
update_interval: 30sec
graph_span: 24h
span:
start: day
now:
show: true
color: red
header:
show: true
title: Energie vandaag
show_states: false
colorize_states: true
apex_config:
chart:
height: 600px
tooltip:
intersect: false
custom: |
EVAL:function({series, seriesIndex, dataPointIndex, w}) {
const hoverX = w.globals.seriesX[seriesIndex][dataPointIndex];
function nearestIndex(xValues, target) {
if (!xValues || xValues.length === 0) {
return -1;
}
let bestIndex = 0;
let bestDiff = Math.abs(xValues[0] - target);
for (let i = 1; i < xValues.length; i++) {
const diff = Math.abs(xValues[i] - target);
if (diff < bestDiff) {
bestDiff = diff;
bestIndex = i;
}
}
return bestIndex;
}
function previousIndex(xValues, target) {
if (!xValues || xValues.length === 0) {
return -1;
}
let index = 0;
for (let i = 0; i < xValues.length; i++) {
if (xValues[i] <= target) {
index = i;
} else {
break;
}
}
return index;
}
function formatValue(name, value) {
if (
value === null ||
value === undefined ||
Number.isNaN(Number(value))
) {
return '—';
}
if (name === 'Battery') {
return `${Number(value).toFixed(0)} %`;
}
if (name === 'Frank prijs') {
return `${Number(value).toFixed(2)} ct/kWh`;
}
return `${Number(value).toFixed(2)} kW`;
}
const rows = [];
for (let i = 0; i < w.globals.seriesNames.length; i++) {
const name = w.globals.seriesNames[i];
const xValues = w.globals.seriesX[i];
let index;
if (name === 'Frank prijs') {
index = previousIndex(xValues, hoverX);
} else {
index = nearestIndex(xValues, hoverX);
}
const value =
index >= 0 && w.globals.series[i]
? w.globals.series[i][index]
: null;
let markerColor = '#999999';
if (name === 'Battery') {
markerColor = '#A0C770';
} else if (name === 'Huis') {
markerColor = '#9B59B6';
} else if (name === 'PV') {
markerColor = '#FABF00';
} else if (name === 'Netimport') {
markerColor = '#C18349';
} else if (name === 'Netexport') {
markerColor = '#FF5722';
} else if (name === 'Frank prijs') {
markerColor = '#00B7FF';
}
rows.push(`
<div style="
display:flex;
align-items:center;
justify-content:space-between;
gap:18px;
margin:4px 0;
white-space:nowrap;
">
<span style="
display:flex;
align-items:center;
gap:6px;
">
<span style="
width:9px;
height:9px;
border-radius:50%;
background:${markerColor};
display:inline-block;
"></span>
${name}
</span>
<strong>
${formatValue(name, value)}
</strong>
</div>
`);
}
const date = new Date(hoverX);
const timeText = date.toLocaleString('nl-NL', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
return `
<div style="
padding:10px 12px;
min-width:210px;
">
<div style="
font-weight:600;
margin-bottom:8px;
border-bottom:1px solid rgba(128,128,128,0.25);
padding-bottom:6px;
">
${timeText}
</div>
${rows.join('')}
</div>
`;
}
grid:
padding:
left: 10
right: 10
bottom: 20
stroke:
width: 2
dataLabels:
enabled: false
legend:
show: true
position: bottom
horizontalAlign: center
offsetY: 5
annotations:
yaxis:
- 'y': 0
strokeDashArray: 0
borderWidth: 2
all_series_config:
type: area
stroke_width: 2
opacity: 0.9
curve: smooth
series:
- entity: sensor.alphaess_inverter_battery_state_of_charge
name: Battery
yaxis_id: battery
type: area
color: '#A0C770'
extend_to: now
curve: smooth
fill_raw: last
stroke_width: 3
opacity: 0.18
group_by:
func: avg
duration: 5min
show:
legend_value: false
- entity: sensor.alphaess_inverter_current_house_load
name: Huis
yaxis_id: power
type: area
color: '#9B59B6'
unit: kW
float_precision: 2
transform: return x / 1000;
extend_to: now
curve: smooth
fill_raw: last
stroke_width: 2.5
opacity: 0.15
group_by:
func: avg
duration: 5min
show:
legend_value: false
- entity: sensor.alphaess_inverter_current_pv_production
name: PV
yaxis_id: power
type: area
color: '#FABF00'
unit: kW
float_precision: 2
transform: return x / 1000;
extend_to: now
curve: smooth
fill_raw: last
stroke_width: 2.5
opacity: 0.15
group_by:
func: avg
duration: 5min
show:
legend_value: false
- entity: sensor.alphaess_inverter_grid_power
name: Netimport
yaxis_id: power
type: area
color: '#C18349'
unit: kW
float_precision: 2
transform: 'return x > 0 ? x / 1000 : 0;'
extend_to: now
curve: smooth
fill_raw: last
stroke_width: 1.5
opacity: 0.22
group_by:
func: min
duration: 5min
show:
legend_value: false
- entity: sensor.alphaess_inverter_grid_power
name: Netexport
yaxis_id: power
type: area
color: '#FF5722'
unit: kW
float_precision: 2
transform: 'return x < 0 ? Math.abs(x) / 1000 : 0;'
extend_to: now
curve: smooth
fill_raw: last
stroke_width: 1.5
opacity: 0.22
group_by:
func: avg
duration: 5min
show:
legend_value: false
- entity: sensor.frank_prices_today
name: Frank prijs
yaxis_id: price
type: line
curve: stepline
color: '#00B7FF'
stroke_width: 4
opacity: 1
unit: ct/kWh
float_precision: 2
data_generator: |
return entity.attributes.prices.map(p => [
new Date(p.from).getTime(),
p.total_price_eur_kwh * 100
]);
show:
legend_value: false
yaxis:
- id: power
min: 0
max: 15
decimals: 2
apex_config:
tickAmount: 15
title:
text: POWER
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(2) + " kW";
}
- id: price
min: '|-1|'
max: '|+1|'
decimals: 0
opposite: false
apex_config:
offsetX: -45
tickAmount: 10
title:
text: PRIJS
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(0) + " ct";
}
- id: battery
min: 0
max: 100
decimals: 0
opposite: true
apex_config:
tickAmount: 10
title:
text: BAT
labels:
formatter: |
EVAL:function(value) {
return value.toFixed(0) + " %";
}
grid_options:
columns: full |
Rondom de config:
DAO config batterycode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| "battery": [
{
"name": "AlphaESS SMILE G3-T10",
"capacity": 18.6,
"entity actual level": "sensor.alphaess_inverter_battery_state_of_charge",
"upper limit": 98,
"lower limit": 15,
"optimal lower level": 20,
"charge stages": [
{ "power": 0, "efficiency": 1 },
{ "power": 10000, "efficiency": 0.95 }
],
"discharge stages": [
{ "power": 0, "efficiency": 1 },
{ "power": 10000, "efficiency": 0.95 }
],
"dc_to_bat efficiency": 1.0,
"bat_to_dc efficiency": 1.0,
"minimum power": 200,
"cycle cost": 0.0175,
"entity set power feedin": "input_number.simulatie_batterij_vermogen",
"entity set operating mode": "input_select.dao_batterij_operating_mode"
}
] |
2 helpers aangemaakt:
input_number.simulatie_batterij_vermogen
-10000
10000
stapgrootte 1
Meeteenheid W
input_select.dao_batterij_operating_mode
Aan
Uit
Modbus-integratie toegevoegd:
https://github.com/senalse/ha-alphaess-modbus
Deze voegt deze entiteiten toe:
switch.alphaess_inverter_dispatch
select.alphaess_inverter_dispatch_mode
number.alphaess_inverter_dispatch_power
number.alphaess_inverter_dispatch_duration
number.alphaess_inverter_dispatch_stop_at_soc
Entiteiten die meetwaarden opleveren:
sensor.alphaess_inverter_battery_state_of_charge
sensor.alphaess_inverter_battery_power
sensor.alphaess_inverter_grid_power
sensor.alphaess_inverter_current_house_load
sensor.alphaess_inverter_current_pv_production
Automatisering die de batterij aanstuurt: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
| alias: DAO naar AlphaESS
description: Stuurt DAO operating mode en vermogenssetpoint naar AlphaESS Dispatch
triggers:
- trigger: state
entity_id:
- input_select.dao_batterij_operating_mode
- input_number.simulatie_batterij_vermogen
conditions: []
actions:
- choose:
- conditions:
- condition: state
entity_id: input_select.dao_batterij_operating_mode
state: Uit
sequence:
- action: switch.turn_off
target:
entity_id: switch.alphaess_inverter_dispatch
- conditions:
- condition: state
entity_id: input_select.dao_batterij_operating_mode
state: Aan
sequence:
- action: number.set_value
target:
entity_id: number.alphaess_inverter_dispatch_duration
data:
value: 480
- action: select.select_option
target:
entity_id: select.alphaess_inverter_dispatch_mode
data:
option: State of Charge Control (2)
- action: number.set_value
target:
entity_id: number.alphaess_inverter_dispatch_power
data:
value: >
{% set dao = states('input_number.simulatie_batterij_vermogen')
| float(0) %} {% set power = dao * -0.001 %} {{ [10, [-10,
power] | max] | min }}
- action: number.set_value
target:
entity_id: number.alphaess_inverter_dispatch_stop_at_soc
data:
value: >
{% set dao = states('input_number.simulatie_batterij_vermogen')
| float(0) %} {% if dao > 0 %}
100
{% else %}
15
{% endif %}
- action: switch.turn_on
target:
entity_id: switch.alphaess_inverter_dispatch
mode: restart |
De trigger kijkt naar:
input_select.dao_batterij_operating_mode
input_number.simulatie_batterij_vermogen
en de automatisering zorgt ervoor dat wanneer DAO zijn planning aanpast, de AlphaESS opnieuw wordt aangestuurd.
AlphaESS Self-Consumption setting:
AlphaESS in de Self-Consumption mode gezet, zodat wanneer DAO niet specifiek aan het laden of ontladen is, de batterij gewoon gebruikt wordt voor huisgebruik en opgeladen kan worden via zonnepanelen.
In het kort:
DAO stuurt de 2 helpers aan, de automatisering reageert op de helpers, de automatisering stuurt de batterij aan via modbus
Ik hoop dat je hier iets aan hebt, ik heb deze hele setup samen met AI gedaan en dit voorzichtig aangepakt, bij elke stap enkele testen gedaan om te kijken of een simulatie daadwerkelijk het gewenste resultaat had. Veel succes, het kost wat tijd maar het is écht de moeite waard.