Tilt Venetian Blind Slats to the Percent with Shelly and Home Assistant

Tilt Venetian Blind Slats to the Percent with Shelly and Home Assistant

Set up and calibrate Shelly Slat Control, control position and tilt separately in Home Assistant, avoid automation pitfalls – including my complete blueprints for sun protection and a wake-up routine.

Don’t just move your external venetian blinds up and down with Shelly and Home Assistant – tilt the slats precisely, to the percent. For years this only worked with ugly workarounds. Since firmware 1.5.0 (early 2025), Shelly devices from Gen2 onwards support it natively. I overlooked the feature for more than a year and only found it because of a bug somewhere completely different – today I’m a big fan. Here’s how to set it up, plus the two automations that only flash by in the video, complete and ready to copy.

YouTube Video
To load the video, please click the image. Please note that by doing so, data will be transmitted to YouTube.

Position and tilt: two independent values

With slat control enabled, a cover entity in Home Assistant has two values:

ValueMeaning0 %50 %100 %
Position (current_position)Height of the blindfully closedhalffully open
Tilt (current_tilt_position)Slat angleslats pointing downhorizontalslats pointing up

The key point: the blind stays at a fixed height, and you still control exactly how much light and sun gets through – purely via the slat angle. In the video I show this on my office blind with a Shelly Plus 2PM: I only move the tilt from 20 to 50 to 80 %, and the height stays the same the whole time.

Backstory: my ugly workarounds

This wasn’t always possible natively. My south-facing windows originally had four Shelly 2.5 – first-generation devices that still don’t support tilt. My workaround back then: four custom switches in Home Assistant that sent a very short pulse via the Shelly REST API, 200 milliseconds up or down, to move the slats somewhat precisely. Yes, it was every bit as hacky as it sounds.

Later I replaced them with second-generation devices – but completely missed the new feature in early 2025. My wake-up routine had a similar trick (more below). By now I’ve thrown all of that out.

Which Shellys support tilt? All cover-capable Shellys from Gen2 onwards (Plus and Pro series as well as Gen3/Gen4) with firmware 1.5.0 or later. First-generation devices (e.g. Shelly 2.5) don’t.

Shelly setup: Slat Control

In the Shelly web interface: Cover → Settings → Slat Control.

  • Open Time / Close Time: how long a full slat rotation takes (all the way down to all the way up). Shelly suggests 1.5 s – mine is 1.1 s. Adjust this to your blind, otherwise the calculated percentages will be off.
  • Don’t confuse this with the regular travel-time calibration of the blind. That one runs automatically; the slat time has to be entered manually.
  • Retain Slat Position: keeps the slat angle when the blind moves. If tilt is at 50 % and you move the blind halfway up, Shelly restores 50 % afterwards.
  • Precise Slat Position: according to the docs, this makes slat control even more precise. I haven’t noticed any difference in daily use – if you know what it actually does, let me know in the comments.

The wall switch behaves differently, too: a short press now only moves the slats (in 20 % steps), a long press moves the whole blind as usual. A short press while moving stops it.

How I found the feature: a Matter bug

I didn’t discover Slat Control on my blinds but somewhere completely different: I’m currently testing the new Shelly Gen4 devices, including their Matter support. If you add a Shelly to Home Assistant via Matter and Slat Control is not enabled, Home Assistant doesn’t create a cover entity at all.

The reason: Shelly then reports no value for tilt – null in programming terms, not to be confused with the number 0. That’s exactly what trips up Home Assistant’s Matter integration. As soon as Slat Control is enabled, a real number arrives and the cover entity shows up normally. While debugging this, I came across the setting for the first time.

Update: I contributed a bugfix to Home Assistant for this, shipping with HA 2026.9.3. More on that – and on Matter and Zigbee with the Gen4 devices – in my upcoming videos.

In Home Assistant: attribute and action

  • Read: attribute current_tilt_position on the cover entity
  • Set: cover.set_cover_tilt_position – analogous to cover.set_cover_position
1
2
3
4
5
action: cover.set_cover_tilt_position
target:
  entity_id: cover.office_blind
data:
  tilt_position: 50

Important: move first, wait, then tilt

In theory Home Assistant can combine position and tilt in one call, but not every device handles that cleanly – it caused problems for me. So:

  1. Move the blind (cover.close_cover or cover.set_cover_position)
  2. Wait until it has arrived (wait_template)
  3. Only then call cover.set_cover_tilt_position

Without the wait, the commands can override each other and the blind ends up in a state you never wanted.

Automation 1: sun protection based on the sun’s position

My setup consists of two blueprints. The YAML is identical to the German version of this article – labels and descriptions inside the blueprints are in German.

Blueprint A: is the sun hitting the window?

This blueprint turns on an input_boolean per window front (e.g. input_boolean.sudfenster_beschienen) as soon as the sun actually shines onto the glass. It takes into account the compass direction, the orientation of my house (offset: 23 degrees) and the roof overhang: if the sun is high enough, the overhang shades the window and nothing needs to close.

 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
blueprint:
  name: Fensterbescheinung berechnen
  description: Berechnet ob für eine bestimmte Himmelsrichtung die Sonne auf die Fenster scheint
  domain: automation
  input:
    target_azimuth:
      name: Himmelsrichtung
      description: Die zu betrachtende (ideale) Himmelsrichtung
      selector:
        select:
          options:
            - value: "0"
              label: Nord
            - value: "90"
              label: Ost
            - value: "180"
              label: Süd
            - value: "270"
              label: West
    boolean_entity:
      name: Zustandsschalter
      description: Eine Entity die auf "ein" geschaltet wird, wenn die Sonne auf die Fenster scheint
      selector:
        entity:
          domain: input_boolean
trigger:
  - platform: state
    entity_id:
      - sensor.sun_solar_elevation
condition:
  - condition: numeric_state
    entity_id: sensor.sun_solar_elevation
    above: -1
action:
  - variables:
      target_azimuth: !input target_azimuth
      offset: 23              # house orientation offset from true compass direction
      azimuth: "{{ states('sensor.sun_solar_azimuth')|float }}"
      elevation: "{{ states('sensor.sun_solar_elevation')|float }}"
      roof_overhang: 86       # roof overhang in cm
      window_height: 200      # window height in cm
  - if:
      - condition: template
        value_template: |-
          {% set target_azimuth = target_azimuth|int + offset %}
          {% set delta = (target_azimuth - azimuth)|abs %}
          {{ delta < 90 or delta > 270 }}
      - condition: template
        value_template: >-
          {% set target_azimuth = target_azimuth|int + offset %}
          {% set degrees = 90-(azimuth-target_azimuth)|abs %}
          {% set effective_roof_overhang = roof_overhang / sin(degrees / 180 *
          3.1415) %}
          {% set max_elevation = (atan(window_height/effective_roof_overhang)) /
          3.1415 * 180 %}
          {{ 0 < elevation and elevation < max_elevation }}
    then:
      - service: input_boolean.turn_on
        data: {}
        target:
          entity_id: !input boolean_entity
    else:
      - service: input_boolean.turn_off
        data: {}
        target:
          entity_id: !input boolean_entity
mode: single

sensor.sun_solar_azimuth and sensor.sun_solar_elevation come from the built-in Sun integration (you may need to enable them). Adjust offset, roof_overhang and window_height to your house.

Blueprint B: close the blind and tilt the slats

This blueprint reacts to the input_boolean from blueprint A: when the sun hits the window, the blind closes, waits until it’s really closed and then tilts the slats to the configured value. When the sun moves away, it opens again – but only if the automation closed it in the first place.

  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
blueprint:
  name: Rollos nach Sonnenstand schliessen und öffnen
  description: Schliesst und öffnet Rollos abhängig von einer Boolean Entity und optional Fensterkontakten
  domain: automation
  input:
    sun_hits_window_entity:
      name: Sonnenstandsindikator
      selector:
        entity:
          domain: input_boolean
    window_open_entity:
      name: Fensterkontakt
      default: []
      selector:
        entity:
          domain: binary_sensor
    cover:
      name: Rolladen
      selector:
        entity:
          domain: cover
    add_conditions:
      name: Weitere Bedingungen
      default: []
      selector:
        condition:
    tilt_after_close:
      name: Tilt nach dem Schliessen
      description: >-
        Optional: Lamellen-Position (%), auf die nach dem Schliessen
        gekippt wird. Leer lassen, um den Tilt unangetastet zu lassen.
      default: -1
      selector:
        number:
          min: -1
          max: 100
          step: 1
          mode: slider
trigger:
  - platform: state
    entity_id:
      - !input sun_hits_window_entity
    from: "off"
    to: "on"
    id: sun_true
  - platform: state
    entity_id:
      - !input sun_hits_window_entity
    from: "on"
    to: "off"
    id: sun_false
  - platform: state
    entity_id: !input window_open_entity
    from: "on"
    to: "off"
    id: closed
  - platform: state
    entity_id:
      - binary_sensor.sonne_scheint
    from: "off"
    to: "on"
    id: brightness_level
condition: !input add_conditions
action:
  - choose:
      - conditions:
          - condition: trigger
            id:
              - sun_true
              - closed
              - brightness_level
          - condition: state
            entity_id: !input sun_hits_window_entity
            state: "on"
          - condition: state
            entity_id: !input cover
            state: open
          - condition: template
            value_template: >-
              {{ cover not in
              states.sensor.automation_cover_state.state }}
            alias: Cover wurde heute noch nicht automatisiert geschlossen
          - condition: state
            entity_id: !input window_open_entity
            state: "off"
          - condition: state
            entity_id: binary_sensor.sonne_scheint
            state: "on"
        sequence:
          - service: cover.close_cover
            data: {}
            target:
              entity_id: !input cover
          - if:
              - condition: template
                value_template: "{{ tilt_after_close|int(-1) >= 0 }}"
            then:
              - wait_template: "{{ is_state(cover, 'closed') }}"
                timeout:
                  seconds: 60
                continue_on_timeout: true
              - service: cover.set_cover_tilt_position
                data:
                  tilt_position: "{{ tilt_after_close|int(-1) }}"
                target:
                  entity_id: !input cover
      - conditions:
          - condition: trigger
            id:
              - sun_false
          - condition: template
            value_template: >-
              {{ cover in
              states.sensor.automation_cover_state.state }}
            alias: Cover wurde heute automatisiert geschlossen
        sequence:
          - service: cover.open_cover
            data: {}
            target:
              entity_id: !input cover
variables:
  cover: !input cover
  tilt_after_close: !input tilt_after_close
mode: restart

Two entities are specific to my house – replace or remove them:

  • binary_sensor.sonne_scheint (“sun is shining”) – a brightness sensor so nothing closes on cloudy days.
  • sensor.automation_cover_state – remembers which blinds were closed automatically today. That way the automation never opens a blind you closed yourself, and never closes one again that you opened manually.

Using the blueprint looks like this:

1
2
3
4
5
6
7
8
- alias: "Blinds: living room south – close/open based on sun position"
  use_blueprint:
    path: homeassistant/sun_close_blinds.yaml
    input:
      sun_hits_window_entity: input_boolean.sudfenster_beschienen
      window_open_entity: binary_sensor.window_contact_living_room_south
      cover: cover.blind_living_room_south
      tilt_after_close: 50

tilt_after_close: 50 sets the slats horizontal. Depending on the window and the incoming light, a different value may work better.

Automation 2: wake-up routine with daylight

My wake-up routine gently brings up lights and radio in the morning. The last step used to be an ugly hack: if the blind was fully closed, it was moved to position 1 % so some light could come through the slat gaps. It sort of worked, but it wasn’t real tilt.

Now the blind stays closed and only the slats are tilted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
- alias: Tilt slats to 50% (if fully closed)
  if:
    - condition: state
      entity_id: cover.bedroom_blind
      attribute: current_position
      state: 0
  then:
    - alias: Set tilt to 50%
      action: cover.set_cover_tilt_position
      target:
        entity_id: cover.bedroom_blind
      data:
        tilt_position: 50
      continue_on_error: true

The current_position: 0 condition makes sure an already opened blind isn’t touched. continue_on_error prevents the whole routine from aborting if the Shelly happens to be unreachable.

The rest of the routine, in short:

  1. 10 minutes after the trigger: dim up the bedroom lights over 20 minutes (transition: 1200)
  2. Set Sonos volume to 0 and start my favorite station – with up to 3 attempts until the player actually reports playing
  3. Raise the volume in 8 steps, 2 minutes apart
  4. Tilt the slats (see above)

My takeaway

The feature has been in the firmware since early 2025 – and it’s worth browsing your Shelly settings every now and then, even if you think you know your device inside out.

Two questions for you: do you know what Precise Slat Position actually does? And do you have blinds that could tilt, but you’re not using it yet? Let me know in the comments!

If you haven’t automated your blinds with Shelly at all yet, start with my article Make Blinds Smart with Shelly.

Ad: Want to get a Shelly? Use the code SMARTHOMEABERSICHER-10OFF for 10 % off via this link (affiliate link).

Shelly supplied the Gen4 devices briefly shown in the video free of charge. Shelly had no influence on the content of this article.

Note: Links marked with affiliate link are affiliate links. As a partner of various retailers and manufacturers (e.g. Amazon, OBI via the Awin network, Shelly) I earn from qualifying purchases. This means I receive a small commission if you purchase through these links — at no extra cost to you. The revenue helps me run this blog and YouTube channel and keep creating content. Thank you for your support!

Joachim
Built with Hugo
Theme Stack designed by Jimmy
Build: 2026-09-17 08:37 UTC