Ik moet wel zeggen dat ik graag had gezien dat de Button+ (ik heb een v1) voor €10 meer gewoon een fatsoenlijke hoeveelheid CPU/RAM aan boord had, het is allemaal kantje boord.
Ik (en Codex) heb een TinySVG renderer geschreven voor HomeAssistant op basis van Nordpool. Best geinig, grafiekje dat de uurprijzen over de loop van de dag enigszins laat zien, en de nacht kleurt.. Publiceert op MQTT.
Je kan deze code natuurlijk ook misbruiken om andere statistiek mee te plotten. Er zitten wat truukjes in om de SVG zo klein mogelijk te houden, bijvoorbeeld het groeperen van lijn segmenten ipv punten, integer coordinaten, alleen SVG primitives, geen gebruik van transparantie, de kwartieren zijn uitgemiddeld tot uren om punten te besparen.
Ik had eerst een gewone 24 uur grafiek, met een bewegende "nu" lijn, maar vond ik minder dan dit, een sliding window van X uur achteruit en X uur vooruit.
Afhankelijk van hoeveel je al op je layout hebt staan of niet gaat hij het renderen. Eerst renderde ik 25 uur en een stepline (2 punten per uur) ipv linegraph, en dat vond hij niet geweldig leuk. Hij renderd nu nog een raar grijs blokjes links van de kwh prijs. Dus beetje mee prutsen.
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
| {%- set svg_width = 100 -%}
{%- set svg_height = 42 -%}
{%- set stroke_width = 4 -%}
{%- set past_hours = 6 -%}
{%- set future_hours = 12 -%}
{%- set night_color = '#3c4246' -%}
{%- set today = state_attr('sensor.nordpool_including_tax', 'raw_today') | default([], true) -%}
{%- set tomorrow = state_attr('sensor.nordpool_including_tax', 'raw_tomorrow') | default([], true) -%}
{%- set raw = today + tomorrow -%}
{%- set now_hour = now().hour -%}
{%- set start_hour = now_hour - past_hours -%}
{%- set end_hour = now_hour + future_hours -%}
{%- set point_count = past_hours + 1 + future_hours -%}
{%- set ns = namespace(hours=[], prices=[], nights=[], paths=[]) -%}
{%- for hour in range(start_hour, end_hour + 1) -%}
{%- if hour >= 0 -%}
{%- set values = namespace(items=[]) -%}
{%- for index in range(hour * 4, hour * 4 + 4) -%}
{%- if raw[index] is defined and raw[index].value is defined -%}
{%- set values.items = values.items + [raw[index].value | float] -%}
{%- endif -%}
{%- endfor -%}
{%- if values.items | count > 0 -%}
{%- set price = (values.items | sum) / (values.items | count) -%}
{%- set ns.hours = ns.hours + [{'hour': hour, 'price': price}] -%}
{%- set ns.prices = ns.prices + [price] -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{%- set min_price = ns.prices | min -%}
{%- set max_price = ns.prices | max -%}
{%- set price_range = max_price - min_price -%}
{%- set padding = stroke_width / 2 -%}
{%- set x_step = (svg_width - stroke_width) / (point_count - 1) -%}
{%- set max_y = svg_height - padding -%}
{%- set y_range = svg_height - stroke_width -%}
{%- set plotted = namespace(points=[]) -%}
{%- for item in ns.hours -%}
{%- set relative_hour = item.hour - start_hour -%}
{%- set x = (padding + (relative_hour * x_step)) | round(0) | int -%}
{%- set y = ((svg_height / 2) if price_range == 0 else max_y - (((item.price - min_price) / price_range) * y_range)) | round(0) | int -%}
{%- set plotted.points = plotted.points + [{'x': x, 'y': y, 'hour': item.hour, 'price': item.price}] -%}
{%- endfor -%}
{%- set night = namespace(active=false, start=0) -%}
{%- for hour in range(start_hour, end_hour + 2) -%}
{%- set is_night = hour >= 0 and hour <= end_hour and (hour % 24) < 7 -%}
{%- if is_night and not night.active -%}
{%- set night.active = true -%}
{%- set night.start = hour -%}
{%- elif not is_night and night.active -%}
{%- set x1 = padding + ((night.start - start_hour) * x_step) - (x_step / 2) -%}
{%- set x2 = padding + ((hour - start_hour) * x_step) - (x_step / 2) -%}
{%- set x = 0 if x1 < 0 else x1 -%}
{%- set x_end = svg_width if x2 > svg_width else x2 -%}
{%- set width = x_end - x -%}
{%- if width > 0 -%}
{%- set x = x | round(0) | int -%}
{%- set width = width | round(0) | int -%}
{%- set ns.nights = ns.nights + ['<path fill="' ~ night_color ~ '" d="M' ~ x ~ ' 0h' ~ width ~ 'v' ~ svg_height ~ 'H' ~ x ~ 'z"/>'] -%}
{%- endif -%}
{%- set night.active = false -%}
{%- endif -%}
{%- endfor -%}
{%- set group = namespace(color='', d='') -%}
{%- for point in plotted.points[1:] -%}
{%- set previous = plotted.points[loop.index0] -%}
{%- set is_past = point.hour <= now_hour -%}
{%- set color =
'#13272d' if is_past and point.price < 0 else
'#224c47' if is_past and point.price < 0.26 else
'#7d7040' if is_past and point.price < 0.4 else
'#6a3627' if is_past else
'#264d59' if point.price < 0 else
'#43978d' if point.price < 0.26 else
'#f9e07f' if point.price < 0.4 else
'#d46c4e'
-%}
{%- if group.color == color -%}
{%- set group.d = group.d ~ 'L' ~ point.x ~ ' ' ~ point.y -%}
{%- else -%}
{%- if group.d != '' -%}
{%- set ns.paths = ns.paths + ['<path d="' ~ group.d ~ '" stroke="' ~ group.color ~ '"/>'] -%}
{%- endif -%}
{%- set group.color = color -%}
{%- set group.d = 'M' ~ previous.x ~ ' ' ~ previous.y ~ 'L' ~ point.x ~ ' ' ~ point.y -%}
{%- endif -%}
{%- endfor -%}
{%- if group.d != '' -%}
{%- set ns.paths = ns.paths + ['<path d="' ~ group.d ~ '" stroke="' ~ group.color ~ '"/>'] -%}
{%- endif -%}
{%- set now_x = (padding + (past_hours * x_step)) | round(0) | int -%}
<svg width="{{ svg_width }}" height="{{ svg_height }}"><path d="M0 0h{{ svg_width }}v{{ svg_height }}H0z"/>{{ ns.nights | join('') }}<path stroke="red" stroke-width="2" d="M{{ now_x }} 0v{{ svg_height }}"/><g fill="none" stroke-width="{{ stroke_width }}" stroke-linecap="round" stroke-linejoin="round">{{ ns.paths | join('') }}</g></svg> |
Voorbeeld output (zonder "minify")
code:
1
2
3
4
5
6
7
8
9
10
| <svg width="100" height="42">
<path d="M0 0h100v42H0z"/>
<path fill="#3c4246" d="M63 0h37v42H63z"/>
<path stroke="red" stroke-width="2" d="M34 0v42"/>
<g fill="none" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 40L7 40L13 39L18 32L23 24" stroke="#224c47"/>
<path d="M23 24L29 21L34 18" stroke="#7d7040"/>
<path d="M34 18L39 10L45 2L50 5L55 12L61 16L66 17L71 19L77 20L82 20L87 20L93 21L98 21" stroke="#f9e07f"/>
</g>
</svg> |
Achtergrond rect, nacht rect (of twee), line voor "current hour", en dan de uurtarieven paths.
Het is ook wat onderdoorzichtig hoe hoog/breed de Button+ daadwerkelijk rendert. Breedte kan je instellen, maar hoogte gaat op basis van fontsize. Ik vond 100x42 goed passen in 42 breed bij fontsize 3.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| {
"displayitemid": "10",
"x": 65,
"y": 23,
"page": 1,
"boxtype": 1,
"fontsize": 3,
"align": 1,
"width": 42,
"unit": "",
"round": null,
"topics":
[
{
"brokerid": "brk1",
"topic": "tariefsvg",
"payload": "",
"eventtype": 46
}
]
}, |
[
Voor 35% gewijzigd door
_eLMo_ op 19-06-2026 18:57
]