Skip to content

Instantly share code, notes, and snippets.

@lukasvice
Last active May 3, 2024 15:02
Show Gist options
  • Save lukasvice/b364724d84c3ac4e160f7a7d8fa37066 to your computer and use it in GitHub Desktop.
Save lukasvice/b364724d84c3ac4e160f7a7d8fa37066 to your computer and use it in GitHub Desktop.
Home Assistant script to control venetian blinds with Shelly
# Have a look at the blog post about this script:
# https://medium.com/@lukasvice/a-utility-script-for-controlling-venetian-blinds-with-shelly-in-home-assistant-2e5cbf2d8d5f
script:
cover_position_tilt:
mode: parallel
fields:
entity_id:
description: "The cover entity"
example: "cover.X"
position:
description: "Position of the cover"
example: 100
tilt_position:
description: "Tilt position (optional)"
example: 100
sequence:
- alias: "Set variables"
variables:
# Time in ms for a full tilt
tilt_time_ms: 1800
# Time between blinds move commands
cmd_wait_time_ms: 500
_original_position: "{{ state_attr(entity_id, 'current_position') }}"
- alias: "Open/Close tilt depending on current position"
choose:
# When closing
- conditions: "{{ state_attr(entity_id, 'current_position') > position|int }}"
sequence:
# Move to the desired position
- service: cover.set_cover_position
data_template:
entity_id: "{{ entity_id }}"
position: "{{ position|int }}"
# Blinds have to be tilted, if tilt_position is set and tilt_position is not fully closed
- alias: "Check if blinds should be tilted"
condition: template
value_template: "{{ tilt_position is defined and tilt_position != none and tilt_position|int > 0 }}"
# Wait for the blinds to stop (Shelly updates current_position on start/stop)
# Cancel the script if the position was not received after 100 seconds
- wait_for_trigger:
- platform: template
value_template: "{% if state_attr(entity_id, 'current_position') != _original_position %}true{% endif %}"
timeout: 100
continue_on_timeout: false
# If it's not the desired position, the blinds were stopped manually (in this case cancel the script)
- alias: "Check if blinds have reached desired position"
condition: template
value_template: "{% if is_state_attr(entity_id, 'current_position', position|int) %}true{% endif %}"
- delay:
milliseconds: "{{ cmd_wait_time_ms }}"
# Move the blinds the required time for tilting in the original direction
- service: cover.close_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms / 100 * tilt_position|int }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ cmd_wait_time_ms }}"
# Move the blinds the required time for tilting in the opposite direction
- service: cover.open_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms / 100 * tilt_position|int }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
# When opening
- conditions: "{{ state_attr(entity_id, 'current_position') < position|int }}"
sequence:
# Move to the desired position
- service: cover.set_cover_position
data_template:
entity_id: "{{ entity_id }}"
position: "{{ position|int }}"
# Blinds have to be tilted, if tilt_position is set and tilt_position is not fully open
- alias: "Check if blinds should be tilted"
condition: template
value_template: "{{ tilt_position is defined and tilt_position != none and tilt_position|int < 100 }}"
# Wait for the blinds to stop (Shelly updates current_position on start/stop)
# Cancel the script if the position was not received after 100 seconds
- wait_for_trigger:
- platform: template
value_template: "{% if state_attr(entity_id, 'current_position') != _original_position %}true{% endif %}"
timeout: 100
continue_on_timeout: false
# If it's not the desired position, the blinds were stopped manually (in this case cancel the script)
- alias: "Check if blinds have reached desired position"
condition: template
value_template: "{% if is_state_attr(entity_id, 'current_position', position|int) %}true{% endif %}"
- delay:
milliseconds: "{{ cmd_wait_time_ms }}"
# Move the blinds the required time for tilting in the original direction
- service: cover.open_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms / 100 * (100 - tilt_position|int) }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ cmd_wait_time_ms }}"
# Move the blinds the required time for tilting in the opposite direction
- service: cover.close_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms / 100 * (100 - tilt_position|int) }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
# Special case: the blinds are already in the desired position
default:
- alias: "Continue only if blinds are not fully opened or closed"
condition: template
value_template: "{{ state_attr(entity_id, 'current_position') > 0 and state_attr(entity_id, 'current_position') < 100 }}"
- choose:
# When the blinds are almost closed, move up for the tilt time
- conditions: "{{ state_attr(entity_id, 'current_position') < 10 }}"
sequence:
- service: cover.open_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
# When the blinds are open, move down for the tilt time
default:
- service: cover.close_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ tilt_time_ms }}"
- service: cover.stop_cover
data_template:
entity_id: "{{ entity_id }}"
- delay:
milliseconds: "{{ cmd_wait_time_ms }}"
# Trigger event to restart the script with the original parameters
- event: start_cover_position_tilt
event_data:
entity_id: "{{ entity_id }}"
position: "{{ position }}"
tilt_position: "{{ tilt_position }}"
automation:
# Automation triggered by a custom event to restart the script
- id: start_cover_position_tilt
alias: "Start Cover Position Tilt"
mode: parallel
trigger:
- platform: event
event_type: "start_cover_position_tilt"
action:
- service: script.cover_position_tilt
data_template:
entity_id: "{{ trigger.event.data.entity_id }}"
position: "{{ trigger.event.data.position }}"
tilt_position: "{{ trigger.event.data.tilt_position }}"
service: script.cover_position_tilt
data:
entity_id: cover.shelly_XXX
position: 70
tilt_position: 50
@stomko11
Copy link

How do you use it in day to day world. Do you use cover template that calls script, or just using some custom cards.
Can you share configuration?

@xbmcgotham
Copy link

Nice, I am happy that it is working! Of course I would also be happy about a beer, thank you! :) https://www.buymeacoffee.com/lukasvice

Hi lukasvice,

was hoping you could help me. I still have no solution for the external Venetian blinds. I am looking for someone that could test your code with these blinds and a Shelly pro 2 pm especially the tilt. As I don’t have the blinds yet to test. I am happy to send you this Shelly device and
instead of a beer pay you for your time. I am really struggling with this. 🙂

thanks in advance.

@Freshhat
Copy link

How do you use it in day to day world. Do you use cover template that calls script, or just using some custom cards. Can you share configuration?

I'm using it most of the times manual in my dashboard, here is one example card:

# Raffstore
  - view_layout:
      grid-area: "cardc"
    type: horizontal-stack
    cards: 
      - type: custom:button-card
        template: edge
      - type: custom:button-card
        show_icon: false
        show_name: false
        show_label: false
        styles:
          grid:
            - grid-template-areas: "'item1 item1' 'item2 item2' 'item3 item3'"
            - grid-template-columns: 1fr 1fr
            - grid-template-rows: "min-content min-content min-content"
            - row-gap: 10px
          card:
            - border-radius: var(--border-radius)
            - box-shadow: var(--box-shadow)
            - padding: 10px
        custom_fields:
          item1:
            card:
              type: custom:button-card
              template: card_cover
              variables:
                ulm_card_cover_enable_controls: true
                ulm_card_cover_enable_slider: true
                ulm_card_cover_enable_popup: true
                ulm_card_cover_enable_horizontal: true
                ulm_card_cover_name: "Büro"
              entity: cover.raffstore_buero
          item2:
            card:
              type: custom:button-card
              template: list_2_items
              custom_fields:
                item1:
                  card:
                    type: custom:button-card
                    template: card_script
                    variables:
                      ulm_card_script_title: '100'
                      ulm_card_script_icon: 'mdi:percent-outline'
                    tap_action:
                      action: call-service
                      service: script.cover_position_tilt
                      service_data:
                        entity_id: cover.raffstore_buero
                        position: 0
                        tilt_position: 50
                    styles:
                      card:
                        - box-shadow: "none"  
                item2:
                  card:
                    type: custom:button-card
                    template: card_script
                    variables:
                      ulm_card_script_title: '75'
                      ulm_card_script_icon: 'mdi:percent-outline'
                    tap_action:
                      action: call-service
                      service: script.cover_position_tilt
                      service_data:
                        entity_id: cover.raffstore_buero
                        position: 25
                        tilt_position: 50
                    styles:
                      card:
                        - box-shadow: "none"  
          item3:
            card:
              type: custom:button-card
              template: list_2_items
              custom_fields:
                item1:
                  card:
                    type: custom:button-card
                    template: card_script
                    variables:
                      ulm_card_script_title: '50'
                      ulm_card_script_icon: 'mdi:percent-outline'
                    tap_action:
                      action: call-service
                      service: script.cover_position_tilt
                      service_data:
                        entity_id: cover.raffstore_buero
                        position: 50
                        tilt_position: 50
                    styles:
                      card:
                        - box-shadow: "none"  
                item2:
                  card:
                    type: custom:button-card
                    template: card_script
                    variables:
                      ulm_card_script_title: '25'
                      ulm_card_script_icon: 'mdi:percent-outline'
                    tap_action:
                      action: call-service
                      service: script.cover_position_tilt
                      service_data:
                        entity_id: cover.raffstore_buero
                        position: 75
                        tilt_position: 50
                    styles:
                      card:
                        - box-shadow: "none"

In the Dashboard it looks like this:
image

But it's also part of my automations, here is an example, when it's getting to hot outside, normally all blinds will close completley expect the office one as i'm working from home:

description: Close all Raffstore if Temp is above 23 °C
trigger:
  - platform: numeric_state
    entity_id: sensor.gw2000a_v2_1_8_outdoor_temperature
    for:
      hours: 0
      minutes: 0
      seconds: 0
    above: 23
    alias: Check Temp > 23C
condition:
  - condition: and
    conditions:
      - condition: time
        after: "09:00:00"
        before: "20:00:00"
        weekday:
          - sun
          - mon
          - tue
          - wed
          - thu
          - fri
          - sat
      - condition: numeric_state
        entity_id: sensor.gw2000a_v2_1_8_wind_speed
        alias: Check Windspeed > 16.0 m/s
        below: 16
    alias: Time & Wind Condition
action:
  - parallel:
      - choose:
          - conditions:
              - condition: numeric_state
                entity_id: cover.raffstore_buero
                attribute: current_position
                below: 100
                enabled: false
              - condition: state
                entity_id: cover.raffstore_buero
                state: open
            sequence:
              - service: script.cover_position_tilt
                data:
                  entity_id: cover.raffstore_buero
                  position: 2
                  tilt_position: 40
        alias: Buero Action
      - choose:
          - conditions:
              - condition: numeric_state
                entity_id: cover.raffstore_kinderzimmer
                attribute: current_position
                below: 100
                enabled: false
              - condition: state
                entity_id: cover.raffstore_kinderzimmer
                state: open
            sequence:
              - service: cover.close_cover
                data: {}
                target:
                  entity_id: cover.raffstore_kinderzimmer
        alias: Kinderzimmer Action
      - choose:
          - conditions:
              - condition: numeric_state
                entity_id: cover.raffstore_schlafzimmer_fenste
                attribute: current_position
                below: 100
                enabled: false
              - condition: state
                entity_id: cover.raffstore_schlafzimmer_fenste
                state: open
            sequence:
              - service: cover.close_cover
                data: {}
                target:
                  entity_id: cover.raffstore_schlafzimmer_fenste
        alias: SZ Fenster Action
      - choose:
          - conditions:
              - condition: numeric_state
                entity_id: cover.raffstore_schlafzimmer
                attribute: current_position
                below: 100
                enabled: false
              - condition: state
                entity_id: cover.raffstore_schlafzimmer
                state: open
            sequence:
              - service: cover.close_cover
                data: {}
                target:
                  entity_id: cover.raffstore_schlafzimmer
        alias: SZ Fenster
mode: single

@lukasvice
Copy link
Author

@stomko11 I only use it in automations as a script call, similar to @Freshhat's example.
@Freshhat This looks great, thanks for sharing!

@lukasvice
Copy link
Author

@xbmcgotham You don't need to send me your Shelly, because it works with every Shelly 2PM. It is not installed on the Shelly itself, but everything runs on Home Assistant. You have to set the tilt time in the script, so you can only test it once you have the blinds and your Shelly is connected to Home Assistant.

@xbmcgotham
Copy link

xbmcgotham commented May 15, 2023 via email

@Johnson145
Copy link

Johnson145 commented Jun 17, 2023

Just tried out the script, too.

Similar to a previous comment

My installation had just issues with the first line in your cover_posistion_titlt.yaml.

I'm also having troubles with that line. I simply had to remove it.

@MelleD
Copy link

MelleD commented Aug 11, 2023

@lukasvice

Thanks for the script. I think I can understand the code quite well.

However, the special case does not work for me. Are some conditions or cases still missing?

  1. If current position and current tilt position is the same the scrip shouldn't do anything?
  2. If the position is the same as the current position, then why are we making the tilt one full move until the tilt_time_ms?
    After that, the blinds are simply completely closed again. here one would have to approach the current tilt position and/or the new tilt position somehow.
      default:
         - alias: "Continue only if blinds are not fully opened or closed"
           condition: template
           value_template: "{{ state_attr(entity_id, 'current_position') > 0 and state_attr(entity_id, 'current_position') < 100 }}"
         - choose:
             # When the blinds are almost closed, move up for the tilt time
             - conditions: "{{ state_attr(entity_id, 'current_position') < 10 }}"
               sequence:
                 - service: cover.open_cover
                   data_template:
                     entity_id: "{{ entity_id }}"
                 - delay:
                     milliseconds: "{{ tilt_time_ms }}"
                 - service: cover.stop_cover
                   data_template:
                     entity_id: "{{ entity_id }}"

Maybe it doesn't even make sense what I'm saying and you have a little input :)

@lukasvice
Copy link
Author

Hi @MelleD,

I see what you mean, and I think both questions are based on the same problem: it's not possible to know the current tilt position. If the blinds have been moved outside the script, it's impossible to know the tilt position. That's why we always have to move the blinds to get to a known tilt state, before moving the blinds to the desired tilt position. I hope that was understable.

However, last week I tried something to optimise exactly this behaviour: After the script has finished, I persist the current position and tilt position. Then, the next time the script runs, it can check if the blinds are currently in the same position and will not move. If the blinds are moved outside the script, the persisted position is erased for the reason described above. It seems to be quite stable, so I will update the solution here on GitHub with the new version soon.

Let me know if you have any further questions or ideas.

@MelleD
Copy link

MelleD commented Aug 11, 2023

Hey @lukasvice,

That's why we always have to move the blinds to get to a known tilt state, before moving the blinds to the desired tilt position. I hope that was understable.

That's clear to me

If the blinds are moved outside the script, the persisted position is erased for the reason described above.

I would have gone in that direction too and make totally sense. How did you delete persisted till position?

Let me know if you have any further questions or ideas.

I'm trying to organize my thoughts because it's hard to explain :-). Whether I don't know if it is technically feasible with HA. I think you're a lot further than me ;)

Currently I wrapped my shelly cover in my own cover template, so that I already have the current tilt position which is set from the UI.

I have 1 to 1 the same thought, that the current tilt position must be set to 0 whenever cover close, cover open or cover set position is executed. Maybe you have a hint how to do that.

So far so good. What I am currently still unsure whether I would really put position and tilt position together in one script or in two separate actions
For example, if I look at the mushroom cover card, these are 2 different actions, but maybe that doesn't have any effect.

My process (as I said unsure if technically possible with HA).

Let's assume resetting tilt position works.
Action 1 set position:
1a. Go to position
2a. If possible, save in an extra attribute in which direction the roller shutter was last moved (up or down)
3a. The roller shutter is in the correct position
4a. Set current tilt position to zero or -1

Set the tilt position :
1b. Calculate how much time (ms) one tilt step needs based on a full tilt ms duration (First thought full tilt / 100)
2b. Based on the current tilt position, calculate the difference that needs to be moved (e.g if you just change the tilt position without moving the shutter and current tilt position is not zero)
3b. Move the cover from the position in the opposite direction to the saved driving direction from step 2a
4b. Duration of movement is difference from 2b * tilt_step_ms from 1b

@lukasvice
Copy link
Author

lukasvice commented Aug 15, 2023

@MelleD,

I'm pretty sure you've seen my blog post about this script - if not, it's linked here :)

You can split up your scripts, sounds reasonable. With the new Shelly Plus 2 PM firmware version 1.0.0 you can get the last_direction directly from the device. But even with this information, you can't be sure if it's fully tilted or if it's only moved for 0.2 seconds, so maybe it's half tilted. Maybe I'm missing something, but I haven't found a way how to use this information. Anyone with good ideas is welcome! :)

To store the last position and tilt position, I use a trigger-based template sensor that stores the data of each cover as JSON. This script can be triggered by two events:

  • Custom (manual): This event is fired by the end of the cover_position_tilt script and the entity_id, position, and tilt_position are passed as arguments
  • A cover changes to opening or closing: This resets the stored position in the data to unknown

At the beginning of the cover_position_tilt script, I read the stored position from the JSON and compare it to the desired position. If it's the same, the script stops, otherwise it continues as normal.

I don't think this is the prettiest approach, but I couldn't think of a better one - and it works :) As I said, I'm open to any ideas and improvements.

Here's the template sensor:

template:
  - trigger:
      - id: "cover_position_tilt"
        platform: event
        event_type: "cover_position_tilt"
      - platform: state
        entity_id:
          - cover.shelly_2_5_COVER_ID
          - cover.shelly_2_5_COVER_ID
          - cover.shelly_2_5_COVER_ID
        to:
          - "opening"
          - "closing"
    sensor:
      - name: "cover_states"
        state: >
          {% set current = ('{}' if states('sensor.cover_states') == 'unknown' else states('sensor.cover_states')) | from_json %}
          {% if trigger.id == "cover_position_tilt" -%}
            {% set new = {
              trigger.event.data.entity_id: {
                'position': trigger.event.data.position,
                'tilt_position': trigger.event.data.tilt_position
              }
            } %}
          {%- else -%}
            {% set new = {
              trigger.entity_id: 'unknown'
            } %}
          {%- endif %}
          {{ dict(current, **new) | to_json }}

This is how to trigger it manually at the end of the open and close movements in the script:

- event: cover_position_tilt
  event_data:
    entity_id: "{{ entity_id }}"
    position: "{{ position }}"
    tilt_position: "{{ tilt_position }}"

And this is the comparison at the beginning of the script:

- alias: "Set variables"
  variables:
    _last_position: >
      {% set cover_states = ('{}' if states('sensor.cover_states') == 'unknown' else states('sensor.cover_states')) | from_json %}
      {{ cover_states[entity_id].position if entity_id in cover_states and 'position' in cover_states[entity_id] else 'unknown' }}
    _last_tilt_position: >
      {% set cover_states = ('{}' if states('sensor.cover_states') == 'unknown' else states('sensor.cover_states')) | from_json %}
      {{ cover_states[entity_id].tilt_position if entity_id in cover_states and 'tilt_position' in cover_states[entity_id] else 'unknown' }}
- condition: template
  value_template: >
     {{ _last_position == 'unknown' or _last_tilt_position == 'unknown' or position != _last_position or tilt_position != _last_tilt_position }}

@lukasvice
Copy link
Author

Thanks for all the feedback on the first line that was causing problems - I have removed it from the code!

@MelleD
Copy link

MelleD commented Aug 15, 2023

@lukasvice

Exactly I know your links and the problems :-)

I am currently creating the temporary data in a temp sensor because the data was always deleted from the original sensor. Maybe there's a better way. In addition, I create a wrapper cover for the blinds to call the scripts directly.

I can also share my script this week, if you want

I solved it by 6 conditions.

  1. last_action open & current_tilt_position = 0
  2. last_action close & current_tilt_position = 0
  3. last_action open & (current_tilt_position - new_tilt_position) > 0
  4. last_action close & (current_tilt_position - new_tilt_position) > 0
  5. last_action open & (current_tilt_position - new_tilt_position) < 0
  6. last_action close & (current_tilt_position - new_tilt_position) < 0

The only edge case that hasn't been resolved is when someone manually tilts the blinds.

My idea (untested), since I'm already overriding open_cover and stop_cover, I could set the current_tilt_positiona to 50 as soon as the position was offset by just 1%.

@lukasvice
Copy link
Author

@MelleD Sounds promising! Tbh, I didn't fully understand your approach, but I'm very curious to see your solution :)

I do have one question though: Imagine you have the blinds at 70% with a 50% tilt position. Now you move the blinds up to 71%. In reality, this only changes the tilt and not the position. How do you deal with this, or how do you know correct the state?

@MelleD
Copy link

MelleD commented Aug 16, 2023

@lukasvice

Here are currently my try:

Like you, you have to play around with the time a bit.
Maybe I need 2 times.

script:
  open_cover:
    mode: parallel
    fields:
      entity_id:
        description: "The orginal cover entity"
        example: "cover.X"
    sequence:
      - alias: "Set variables"
        variables:
          temp: "_temp"
          entity_id_temp: "{{ entity_id + temp }}"
          current_position: "{{ state_attr(entity_id, 'current_position') }}"
      - service: python_script.set_state_attribute
        data:
          entity_id: "{{entity_id_temp}}"
          state_attr: current_tilt_position
          state: 0
      - service: python_script.set_state_attribute
        data:
          entity_id: "{{entity_id_temp}}"
          state_attr: last_action
          state: "open_cover"
      - service: cover.open_cover
        target:
          entity_id: "{{entity_id}}"
        data: {}

  close_cover:
    mode: parallel
    fields:
      entity_id:
        description: "The orginal cover entity"
        example: "cover.X"
    sequence:
      - alias: "Set variables"
        variables:
          temp: "_temp"
          entity_id_temp: "{{ entity_id + temp }}"
      - service: python_script.set_state_attribute
        data:
          entity_id: "{{entity_id_temp}}"
          state_attr: current_tilt_position
          state: 0
      - service: python_script.set_state_attribute
        data:
          entity_id: "{{entity_id_temp}}"
          state_attr: last_action
          state: "close_cover"
      - service: cover.close_cover
        target:
          entity_id: "{{entity_id}}"
        data: {}

  set_cover_position_tilt:
    mode: parallel
    fields:
      entity_id:
        description: "The cover entity"
        example: "cover.X"
      tilt_position:
        description: "Tilt position"
        example: 100
    sequence:
      - alias: "Set variables"
        variables:
          tilt_full_time_ms: 1200
          temp: "_temp"
          entity_id_temp: "{{ entity_id + temp }}"
          current_position: "{{ state_attr(entity_id, 'current_position') }}"
          current_tilt_position: "{{state_attr(entity_id_temp, 'current_tilt_position')}}"
      - alias: "Continue only if blinds are not fully opened and tilt position is changed"
        condition: template
        value_template: "{{ current_position is not none and (current_position|int) < 100 and current_tilt_position is not none and (tilt_position|int) != current_tilt_position}}"
      - alias: "Move the blinds"
        choose:
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'open_cover' and current_tilt_position == 0}}"
            sequence:
              - service: cover.close_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * tilt_position|int }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - service: python_script.set_state_attribute
                data:
                  entity_id: "{{entity_id_temp}}"
                  state_attr: current_tilt_position
                  state: "{{tilt_position|int}}"
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'close_cover' and current_tilt_position == 0}}"
            sequence:
              - service: cover.open_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * tilt_position|int }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - service: python_script.set_state_attribute
                data:
                  entity_id: "{{entity_id_temp}}"
                  state_attr: current_tilt_position
                  state: "{{tilt_position|int}}"
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'open_cover' and current_position != 0 and (tilt_position-current_tilt_position)>0}}"
            sequence:
              - service: cover.close_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * (tilt_position-current_tilt_position) }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - service: python_script.set_state_attribute
                data:
                  entity_id: "{{entity_id_temp}}"
                  state_attr: current_tilt_position
                  state: "{{tilt_position|int}}"
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'open_cover' and current_position != 0 and (tilt_position-current_tilt_position)<0}}"
            sequence:
              - service: cover.open_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * ((tilt_position-current_tilt_position)*-1) }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'close_cover' and current_position != 0 and (tilt_position-current_tilt_position)>0}}"
            sequence:
              - service: cover.open_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * (tilt_position-current_tilt_position) }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - service: python_script.set_state_attribute
                data:
                  entity_id: "{{entity_id_temp}}"
                  state_attr: current_tilt_position
                  state: "{{tilt_position|int}}"
          - conditions: "{{ state_attr(entity_id_temp, 'last_action') == 'close_cover' and current_position != 0 and (tilt_position-current_tilt_position)<0}}"
            sequence:
              - service: cover.close_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - delay:
                  milliseconds: "{{ tilt_full_time_ms / 100 * ((tilt_position-current_tilt_position)*-1) }}"
              - service: cover.stop_cover
                data_template:
                  entity_id: "{{ entity_id }}"
              - service: python_script.set_state_attribute
                data:
                  entity_id: "{{entity_id_temp}}"
                  state_attr: current_tilt_position
                  state: "{{tilt_position|int}}"

I do have one question though: Imagine you have the blinds at 70% with a 50% tilt position. Now you move the blinds up to 71%. In reality, this only changes the tilt and not the position. How do you deal with this, or how do you know correct the state?

I would like to do the following if the position moves just 1%.

  1. If the current tilt position is 0 I would set it to 50%
  2. If the current tilt position != 0 I would subtract or add 50 (depending on the last action)

2a. If the current tilt position is then over 100% I would set it back to 0.

But here my technical knowledge is too weak how and where to find out that the position moved only 1%.

I'm aware that you can't get a 100% accurate solution with this, but I don't have the use case to change the tilt 4 or 5 times either ;).
Most of the time you do it once, maybe a second time to adjust. That works quite well. It would be important to show the manual status.

With the new Shelly Plus 2 PM firmware version 1.0.0 you can get the last_direction directly from the device

With this information, I can remove some settings but I didn't find this information into HA

@Monacoslo
Copy link

Can you please help me understand how exactly should I use this? Do you have any tutorial? I am also not familiar much about scripts.

I have several external venetian blinds using Shelly 2PM plus and I need to make tilt control in HA.

If I understand correctly, I have to create "script" in HA for each one, copy in it the content after "-script" and rename entity_id to my id of cover.shelly....
Then create automation with content under "-automation"

What next?

@xbmcgotham
Copy link

xbmcgotham commented Aug 23, 2023 via email

@FlyingDodo86
Copy link

FlyingDodo86 commented Oct 12, 2023

I freshly started with HA and Shellys but after hours of figuring out issues I made it work. Here a summary of my conclusions:

In order to get the position of the covers I had to remove the shellys from HA and Shelly App. Then re-integrate them via the Shelly app and the run the Calibration on the Shelly app (Settings->Calibration). Once completed, re-integrated the Shellys in HA. Based on above replies maybe one additional remark:
The cover_position_tilt.yaml does not need to be modified simply e.g. create a new folder "Packages" via Studio Code Server. Then create a new file in the packages folder with name cover_position_tilt.yaml and copy paste the script content. Next is to add the following in the configuration.yaml

homeassistant:
  packages: !include_dir_named packages/

Once done go in Developer Tools and click on Check Configuration. If everything is good restart HA. Testing of the script: In Developer Tools click on Services and paste the below and click on Call Service

service: script.cover_position_tilt
data:
  entity_id: cover.replace_with_your_shelly_entity_ID
  position: 10
  tilt_position: 40

@lukasvice
Copy link
Author

lukasvice commented Oct 12, 2023

@FlyingDodo86 Thanks for sharing.

Note that in the meantime, I have updated the script to exclude the "package" line to make it easier for new users to use it. I still use it as a package though.

@xbmcgotham
Copy link

Hi @lukasvice hope your doing great!, I see you have made great progress. I am just wondering, as I am not a developer, can you confirm to me if this script will do what I am expecting for my setup? :-)

I have the blind boxes installed and all wired up, but have not yet the blinds, so I can still adapt to the best situation.

Like shown on the drawing attached, this is the setup I like and have wired up for. A Shelly (Pro series I have now) unit that is controlled by a manual switch on the wall and the HA. I know that in the past the Shelly HA script needs tweaking to make the tilt work, and I am not sure how this currently works with the wall switches in parallel. I am happy to buy additional WAREMA control system if that would allow for easy integration. I do try to stay away from Wifi or RF and like all wired as some blinds are to far away to reach that way.

Hope you can get back to me.

Appreciated, thanks!

Screenshot 2023-10-24 at 10 12 20

@Kepro
Copy link

Kepro commented Dec 22, 2023

@xbmcgotham yes it will work, checking wiring diagram for shelly 2PM

@xbmcgotham
Copy link

xbmcgotham commented Dec 22, 2023 via email

@MatejMonika
Copy link

Does work with Shelly 2PM Plus

@u20p17
Copy link

u20p17 commented Apr 9, 2024

@FlyingDodo86 Thanks for sharing.

Note that in the meantime, I have updated the script to exclude the "package" line to make it easier for new users to use it. I still use it as a package though.

Hello @lukasvice, I created a package folder and inside this folder I copied the latest cover_position_tilt.yaml. When I add

homeassistant:
  packages: !include_dir_named packages/

to the configuration.yaml and then check the configuration I get the following warning:


 Konfigurationswarnungen
 Setup of package 'script' failed: Integration 'cover_position_tilt' not found.
 Setup of package 'automation' failed: Invalid package definition 'automation': expected a dictionary. Package will not be initialized

What's my issue?

@lukasvice
Copy link
Author

Hi @u20p17, your approach seems fine to me. It looks like script and automation are somehow being interpreted as package names. Are you sure you're not using !include_dir_merge_named (That would work a bit differently)?

You can also look at some examples here and try to compare your configurations: https://www.home-assistant.io/examples/#example-configurationyaml

@u20p17
Copy link

u20p17 commented Apr 9, 2024

@lukasvice, i indeed did use the !include_dir_merge_named… if i delete this line in the configuration.yaml and restart HA i do not see any error/warning, but i can also not see any new script/automation…
something i am doing wrong 🤗

@lukasvice
Copy link
Author

lukasvice commented Apr 9, 2024

@u20p17, there's a difference between !include_dir_named and !include_dir_merge_named. The merge one requires the package name at the beginning of the file. Try using the one without merge, as you wrote in your original comment. See also the documentation on this: https://www.home-assistant.io/docs/configuration/packages/#create-a-packages-folder.

@u20p17
Copy link

u20p17 commented Apr 10, 2024

danke, hatte es tatsächlich falsch^^ jetzt funktionierts (Y)

@u20p17
Copy link

u20p17 commented Apr 17, 2024

today i had some time to play with this script, but in my case it is not working as expected. the problem I think is that my venetian blinds do need different times for a full tilt upwards (1400ms) and downwards (1000ms). Do you have the same issue and just took the middle value? if you send tilt position 50 should the blinds stop at around 45deg?

@lukasvice
Copy link
Author

@u20p17 Hmm, my blinds always take the same amount of time regardless of the direction. Maybe you could modify the script so that you have two variables, tilt_time_opening_ms and tilt_time_closing_ms. You can then use these variables in the "opening" or "closing" condition of the script. This might work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment