Zonwering in Home Assistant automatiseren lijkt simpel: dicht als de zon op het huis staat, open bij regen en wind. Maar mijn screens gingen in oktober de hele dag open en dicht, en de oorzaak zat niet in een sensor maar in de opzet van de automatisering. In dit stuk laat ik zien hoe de oude versie eruitzag, waarom die niet kon werken, en hoe ik hem heb herbouwd met hysterese, een aparte veiligheidslaag en een handmatige stand die vanzelf verloopt.
Dit is een vervolg op Somfy-screens in Home Assistant, waarin ik beschrijf hoe onze Hardol-screens via de Connexoon in Home Assistant terechtkwamen.
Het probleem
De zonwering moest simpel zijn: dicht als de zon op het huis staat en het binnen warm wordt, open bij regen, wind en ’s nachts. Dat werkte in de zomer redelijk. Zodra de verwarming in het najaar aanging, begon het gedoe: schermen die om de paar minuten van stand wisselden, een gezin dat ze handmatig omhoog deed en ze vijf minuten later weer dicht zag gaan, en een logboek vol cover.close_cover-aanroepen.
Een thermostaat die in stappen van 0,5 K meet, een warmtepomp die naar 20 °C regelt en een automatisering met een harde grens op 20,5 °C zijn samen een oscillator. Niet één onderdeel was kapot; de combinatie was het probleem.
De oude automatisering
Dit was de kern. Elke vijf minuten en bij elke sensorwijziging werden variabelen berekend, en werd via choose óf open_cover óf close_cover gestuurd.
triggers:
- trigger: time_pattern
minutes: /5
- trigger: state
entity_id:
- sensor.binnentemperatuur
- sensor.neerslagintensiteit
- sensor.windsnelheid
- sun.sun
actions:
- variables:
temp: "{{ states('sensor.binnentemperatuur') | float(0) }}"
regen: "{{ states('sensor.neerslagintensiteit') | float(0) }}"
wind: "{{ states('sensor.windsnelheid') | float(99) }}"
azimuth: "{{ state_attr('sun.sun', 'azimuth') | float(0) }}"
nacht: "{{ is_state('sun.sun', 'below_horizon') }}"
- choose:
- conditions: "{{ nacht or regen > 0 or wind > 15 or temp < 20.5 }}"
sequence:
- action: cover.open_cover
target: { entity_id: cover.all_screens }
- conditions: "{{ temp > 20 and 60 <= azimuth <= 300 and regen < 1 and wind < 6 and not nacht }}"
sequence:
- action: cover.close_cover
target: { entity_id: cover.all_screens }
mode: restart
Het leest logisch, maar er zitten vijf fouten in die elkaar versterken.
- Geen hysterese. Dicht bij
temp > 20, open bijtemp < 20.5: dat is één harde grens op 20,5 °C. Met een thermostaat die in stappen van 0,5 K meet en een verwarming die naar 20 °C regelt, pendelt de meting structureel tussen 20,0 en 20,5. Elke stap is een commando naar alle schermen. - Stateless. De automatisering kijkt nooit of de schermen al in de gewenste stand staan. Elke run stuurt een commando, ook als er niets verandert. Handmatig ingrijpen is daardoor onmogelijk: binnen vijf minuten wordt het teruggedraaid.
- Trigger op
sun.sunzonderto:. Een state-trigger zonderto:reageert ook op wijzigingen van attributen. De zon-entiteit werkt azimut en elevatie om de paar minuten bij, dus de automatisering draaide veel vaker dan de bedoeling was. - Windgrenzen zonder eenheid. Dicht alleen onder 6, open boven 15. In km/h is 6 bijna windstil en gaan de schermen zelden dicht; in m/s is 15 (7 Bft) veel te hoog als veiligheidsgrens. Fabrikanten zitten rond 10 m/s.
- Zon alleen op azimut. 60–300° is in Nederland de hele dag. Zonder elevatie of een zonkenmerk gaan de schermen op een bewolkte dag dicht zodra het binnen 20,5 °C is, en blokkeren ze in de winter gratis zonnewarmte.
Kleiner, maar niet onbelangrijk: regen | float(0) telt een onbeschikbare regensensor als droog, regen > 0 reageert op meetruis van 0,01 mm/u, en mode: restart voegt bij losse service-calls niets toe.
De aanpak: drie lagen
De oplossing is niet een slimmere choose, maar het uit elkaar trekken van drie vragen die in de oude versie door elkaar liepen:
- Veiligheid: is het veilig om de schermen beneden te hebben?
- Comfort: wil ik ze beneden hebben?
- Aansturing: moet er nu een commando gestuurd worden?
binary_sensor.zonwering_blokkade (veiligheid: wind, regen, nacht) ──┐
binary_sensor.zonwering_gewenst (comfort: zon, temperatuur) ──┼──> automatisering ──> cover.all_screens
input_boolean.zonwering_handmatig (vervalt na 3 uur) ──┘
veiligheid gaat altijd vóór handmatig; comfort niet
De twee binary_sensors rekenen de beslissing uit en bewaren hun eigen toestand. Daardoor wordt hysterese mogelijk. De automatisering wordt dom en voorspelbaar: ze triggert op een wissel, vergelijkt met de werkelijke stand van cover.all_screens en stuurt alleen als die afwijkt. De handmatige stand zit ertussen en blokkeert alleen de comfortlaag, nooit de veiligheid.
Het package
Alles staat in één packagebestand, zonwering_automatisch_regelen.yaml. Vervang de drie sensornamen en cover.all_screens door je eigen entiteiten.
Helpers
De drempels staan in input_number-helpers, zodat je ze op het dashboard kunt bijstellen zonder YAML aan te raken. De handmatige stand is een input_boolean.
input_boolean:
zonwering_handmatig:
name: Zonwering handmatig
icon: mdi:hand-back-right
input_number:
zonwering_binnen_dicht:
name: Zonwering – binnen dicht vanaf (°C)
min: 19
max: 26
step: 0.5
initial: 22.0
unit_of_measurement: "°C"
zonwering_binnen_open:
name: Zonwering – binnen weer open onder (°C)
min: 18
max: 25
step: 0.5
initial: 21.0
unit_of_measurement: "°C"
zonwering_wind_max:
name: Zonwering – wind open boven (m/s)
min: 4
max: 20
step: 0.5
initial: 10
unit_of_measurement: "m/s"
Laag 1: veiligheid
binary_sensor.zonwering_blokkade is aan bij wind, regen of nacht. Onbeschikbare sensoren krijgen een onveilige standaardwaarde (float(99)), zodat een uitgevallen sensor altijd tot “open” leidt. De wind heeft hysterese via this.state: aan boven de maximumwaarde, pas weer uit onder 70 % daarvan. Een delay_off van tien minuten voorkomt dat één windstille minuut de schermen weer naar beneden stuurt.
template:
- binary_sensor:
- name: Zonwering blokkade
unique_id: zonwering_blokkade
icon: mdi:weather-windy
delay_off: "00:10:00"
state: >
{% set wind = states('sensor.windsnelheid') | float(99) %}
{% set regen = states('sensor.neerslagintensiteit') | float(99) %}
{% set elev = state_attr('sun.sun', 'elevation') | float(-10) %}
{% set wind_max = states('input_number.zonwering_wind_max') | float(10) %}
{% set al_aan = this.state == 'on' if this is defined else false %}
{% set wind_grens = (wind_max * 0.7) if al_aan else wind_max %}
{{ wind > wind_grens or regen > 0.2 or elev < 3 }}
Laag 2: comfort
binary_sensor.zonwering_gewenst beantwoordt één vraag: wil ik de schermen nu dicht? Dicht vanaf 22,0 °C, pas weer open onder 21,0 °C: een band van twee thermostaatstappen, ruim boven het stooksetpoint van 20 °C. De zon moet minstens 10° boven de horizon staan, de ochtendrust tot 08:30 blijft, en de blokkade moet uit zijn. delay_on en delay_off werken als debounce.
- name: Zonwering gewenst
unique_id: zonwering_gewenst
icon: mdi:blinds
delay_on: "00:03:00"
delay_off: "00:05:00"
state: >
{% set temp = states('sensor.binnentemperatuur') | float(0) %}
{% set elev = state_attr('sun.sun', 'elevation') | float(0) %}
{% set az = state_attr('sun.sun', 'azimuth') | float(0) %}
{% set dicht = states('input_number.zonwering_binnen_dicht') | float(22) %}
{% set open = states('input_number.zonwering_binnen_open') | float(21) %}
{% set al_aan = this.state == 'on' if this is defined else false %}
{% set ochtendrust_voorbij = now().hour * 60 + now().minute >= 8 * 60 + 30 %}
{% set zon_op_huis = elev > 10 and 60 <= az <= 300 %}
{% set warm = temp >= dicht if not al_aan else temp > open %}
{{ ochtendrust_voorbij and zon_op_huis and warm
and is_state('binary_sensor.zonwering_blokkade', 'off') }}
De regel warm = temp >= dicht if not al_aan else temp > open is de hele hysterese. Staat de sensor uit, dan moet de temperatuur de bovengrens halen. Staat hij aan, dan blijft hij aan tot de ondergrens.
Laag 3: aansturing
De automatisering triggert alleen op een wissel van de twee binary_sensors, op het verlopen van de handmatige stand en bij het opstarten van Home Assistant. Elk kwartier is er een vangnet, maar dat stuurt alleen als de werkelijke stand afwijkt. Veiligheid gaat vóór de handmatige stand; comfort niet.
automation:
- alias: Zonwering Automatisch Regelen
id: zonwering_automatisch_regelen
mode: queued
max: 3
triggers:
- trigger: state
entity_id: binary_sensor.zonwering_blokkade
to: ["on", "off"]
- trigger: state
entity_id: binary_sensor.zonwering_gewenst
to: ["on", "off"]
- trigger: state
entity_id: input_boolean.zonwering_handmatig
to: "off"
- trigger: homeassistant
event: start
- trigger: time_pattern
minutes: "/15"
actions:
- choose:
# A. Veiligheid: altijd open, ook bij handmatig
- conditions:
- condition: state
entity_id: binary_sensor.zonwering_blokkade
state: "on"
- condition: not
conditions:
- condition: state
entity_id: cover.all_screens
state: open
sequence:
- action: cover.open_cover
target: { entity_id: cover.all_screens }
# B. Comfort: dicht
- conditions:
- condition: state
entity_id: binary_sensor.zonwering_blokkade
state: "off"
- condition: state
entity_id: input_boolean.zonwering_handmatig
state: "off"
- condition: state
entity_id: binary_sensor.zonwering_gewenst
state: "on"
- condition: not
conditions:
- condition: state
entity_id: cover.all_screens
state: closed
sequence:
- action: cover.close_cover
target: { entity_id: cover.all_screens }
# C. Comfort: open
- conditions:
- condition: state
entity_id: binary_sensor.zonwering_blokkade
state: "off"
- condition: state
entity_id: input_boolean.zonwering_handmatig
state: "off"
- condition: state
entity_id: binary_sensor.zonwering_gewenst
state: "off"
- condition: not
conditions:
- condition: state
entity_id: cover.all_screens
state: open
sequence:
- action: cover.open_cover
target: { entity_id: cover.all_screens }
- alias: Zonwering handmatig verloopt
id: zonwering_handmatig_verloopt
triggers:
- trigger: state
entity_id: input_boolean.zonwering_handmatig
to: "on"
for: "03:00:00"
actions:
- action: input_boolean.turn_off
target: { entity_id: input_boolean.zonwering_handmatig }
De handmatige stand verloopt na drie uur. Dat is bewust: een override die je vergeet uit te zetten, is over een week een kapotte automatisering.
Testen voordat je het live zet
Een template die er goed uitziet, is nog geen template die werkt. Ik test de twee binary_sensors los van Home Assistant met een klein Python-script en Jinja2, waarin states(), state_attr(), now() en this.state nagebootst worden. Zo reken je in een paar seconden tientallen situaties door, inclusief de randgevallen die je in het echt pas na weken tegenkomt.
Dit zijn de scenario’s die ik heb gedraaid. Alle negentien gaven het verwachte resultaat.
| Scenario | Sensor | Was | Verwacht |
|---|---|---|---|
| Rustig weer, overdag | blokkade | uit | uit |
| Wind 11 m/s | blokkade | uit | aan |
| Wind 8 m/s | blokkade | aan | blijft aan |
| Wind 6,5 m/s | blokkade | aan | uit |
| Wind 8 m/s | blokkade | uit | blijft uit |
| Regen 0,1 mm/u (ruis) | blokkade | uit | uit |
| Regen 0,5 mm/u | blokkade | uit | aan |
| Windsensor unavailable | blokkade | uit | aan |
| Zon onder horizon | blokkade | uit | aan |
| Binnen 20,5 °C (stooksetpoint) | gewenst | uit | uit |
| Binnen 22,0 °C | gewenst | uit | aan |
| Binnen 21,5 °C | gewenst | aan | blijft aan |
| Binnen 21,0 °C | gewenst | aan | uit |
| Binnen 21,5 °C | gewenst | uit | blijft uit |
| Binnen 23 °C om 08:15 | gewenst | uit | uit |
| Binnen 23 °C om 08:30 | gewenst | uit | aan |
| Binnen 23 °C, zon 5° hoog | gewenst | uit | uit |
| Binnen 23 °C, blokkade aan | gewenst | uit | uit |
| Temperatuursensor unavailable | gewenst | aan | uit |
De kern van het testscript:
import jinja2, datetime, yaml
class This:
def __init__(self, state): self.state = state
def run(template, states, attrs, this_state, hour=12, minute=0):
env = jinja2.Environment()
env.globals['states'] = lambda e: states.get(e, 'unavailable')
env.globals['state_attr'] = lambda e, a: attrs.get((e, a))
env.globals['is_state'] = lambda e, v: states.get(e) == v
env.globals['now'] = lambda: datetime.datetime(2026, 10, 8, hour, minute)
def fl(v, default=0):
try: return float(v)
except (TypeError, ValueError): return default
env.filters['float'] = fl
return env.from_string(template).render(this=This(this_state)).strip()
pkg = yaml.safe_load(open('zonwering_automatisch_regelen.yaml'))
gewenst = next(b['state'] for b in pkg['template'][0]['binary_sensor']
if b['name'] == 'Zonwering gewenst')
basis = {'sensor.windsnelheid': '4', 'sensor.neerslagintensiteit': '0',
'input_number.zonwering_wind_max': '10',
'input_number.zonwering_binnen_dicht': '22',
'input_number.zonwering_binnen_open': '21',
'binary_sensor.zonwering_blokkade': 'off'}
zon = {('sun.sun', 'elevation'): 25, ('sun.sun', 'azimuth'): 180}
# hysterese omhoog: 21,5 vanuit 'uit' blijft uit, vanuit 'aan' blijft aan
assert run(gewenst, {**basis, 'sensor.binnentemperatuur': '21.5'}, zon, 'off') == 'False'
assert run(gewenst, {**basis, 'sensor.binnentemperatuur': '21.5'}, zon, 'on') == 'True'
Daarna in Home Assistant zelf: controleer de configuratie, herlaad de template-entiteiten, en laat het package een dag draaien met de automatisering uit. Je ziet dan in de geschiedenis van binary_sensor.zonwering_gewenst of hij zich gedraagt zoals je wilt, zonder dat er een scherm beweegt. Pas daarna zet je de automatisering aan.
Wat ik hiervan heb geleerd
De drie principes die dit package robuust maken, gelden voor bijna elke automatisering die iets fysieks aanstuurt.
- Reken de beslissing uit in een template-sensor, niet in de automatisering. Een binary_sensor met
this.statekan hysterese, heeft gratis geschiedenis in het dashboard en is los te testen. De automatisering wordt dan triviaal: volg de sensor. - Stuur alleen bij een verschil. Vergelijk de gewenste stand met de werkelijke stand en doe niets als die gelijk zijn. Dat maakt handmatig ingrijpen mogelijk en houdt je logboek leesbaar.
- Scheid veiligheid van comfort. Wind en regen mogen nooit door een comfortregel of een handmatige stand overstemd worden. Twee sensors, twee prioriteiten.
Hetzelfde principe zit in mijn kamerthermostaat voor de warmtepomp: een hysterese van 0,5 K en minimale aan- en uittijden, omdat de thermostaten in stappen van 0,5 K meten.
Wat nog komt
- Het actuele vermogen van de zonnepanelen als zonkenmerk in
zonwering_gewenst. Dat is een betere maat voor “de zon schijnt echt” dan alleen de stand van de zon, en de sensor is er al. - Een grens voor de buitentemperatuur, zodat de schermen op een zonnige winterdag open blijven en de warmtepomp de gratis warmte kan gebruiken.
Vragen of verbeteringen? Laat het weten in de reacties.
Dit werkt in mijn situatie: Hardol-screens met Somfy-motoren via een Connexoon, een cover-groep cover.all_screens en Home Assistant. Drempels, sensornamen en windgrenzen van je eigen screens kunnen anders zijn. Controleer het tegen je eigen documentatie en de opgave van de fabrikant.






Geef een reactie