Controlling cover with Curtain Switch with no cover position info exposed

Hi everyone

I’m trying to get this to work but I think I ran out of ideas. Here’s what I got and what I’m trying to do:

  • I got a curtain switch connected via Tuya which doesn’t expose any info about the cover position (I just have the buttons to go up, down and stop)
  • My cover has basically 3 positions: open, close and “shade”
  • The cover motor doesn’t support this, so it doesn’t know where to stop for example to fully close the cover when it is in “shade” position
  • In order to do the above, I purchased a contact sensor which is basically what I’m using to “stop” the cover in the close position. This works pretty well (despite I still have to figure out why the sensor doesn’t report the status from time to time, but that’s another discussion)
  • What I’d like to do now is to get the cover position somehow stored in an helper or variable or whatever to consume it in an automation. I don’t need the “partial position”, I just need to know when it’s open, close and in “shade position”

I was thinking to create an helper which should store this info and that survives an HA restart eventually, then update the value with an automation which will trigger every time the curtain goes up or down. To do this I really have no idea where to start from to be honest…

Just create a template cover. Doing this, you can combine many different sensors, one for each thing really. Tie your sensor into the template position. Now the cover will keep track of its own position.

thanks Jim for the answer! If you have it handy, would you mind to share the code? So that I can have an idea how to make it…

You have 2 options. Use the value_template to report open/close. Or, use the position_template to try to report your 3 states as a number. Can use 0, 50, and 100 for closed, shade, open.

Since your sensor only knows open/closed, I’d recommend using a value template. But if you can figure out a way to determine in-between, you can switch to the position_template.

cover:
  - platform: template
    covers:
      window_shades:
        device_class: shade
        friendly_name: "Window Shades"
        # If the closed sensor is on, report 0. 
        # Else.....figure out how to differentiate between shade and open... (replace the XXXX)
        position_template: >
          {% if is_state('binary_sensor.shade_closed', 'on') %} 0
          {% elif is_state('binary_sensor.shade_cloded', 'off') and XXXXX %} 50
          {% else %} 100
        open_cover:
          service: cover.open_cover
          entity_id: cover.my_shelly_cover
        close_cover:
          service: cover.close_cover
          entity_id: cover.my_shelly_cover
        stop_cover:
          service: cover.stop_cover
          entity_id: cover.my_shelly_cover

Thank you ! I’ll try it asap and let you know

So this is what I’ve done. I created the cover template like this:

cover:
  - platform: template
    covers:
      tapparella_camera:
        device_class: shade    
        friendly_name: "Tapparella Camera"
        position_template: >
          {% if is_state('input_select.posizione_tapparella_camera', 'chiuso') %}
            0
          {% elif is_state('input_select.posizione_tapparella_camera', 'tapparella') %}
            50
          {% else %}
            100
          {% endif %}        
        open_cover:
          service: cover.open_cover
          entity_id: cover.bfdc1128af1eb05af1w4i6
        close_cover:
          service: cover.close_cover
          entity_id: cover.bfdc1128af1eb05af1w4i6
        stop_cover:
          service: cover.stop_cover
          entity_id: cover.bfdc1128af1eb05af1w4i6

and then I created this input helper:

input_select:
  posizione_tapparella_camera:
    name: Posizione Tapparella Camera
    options:
      - aperto
      - chiuso
      - tapparella
    icon: mdi:blinds

Apart the automation I have to build to update the input status with the various options, I was able to get this:
image

Which is fine, but I don’t understand why I don’t see the position slider. When I change the input status it is reflected in the UI, so not sure why I cannot control it. This is needed especially because the only way to put the cover in the “blind position” is to close it when it’s in close position (not sure this is really clear). Any idea how to fix this and some suggestions on how to setup the automation to update the input variable ?

1 Like

You need to define the ‘set_cover_position’ function now to convert a number to a position. That should give you the ability to use the slider.

I’m pretty sure the template cover things (open/close/stop) are actions, not a sequence. So we should create scripts. One for open/close, and one for set position. These scripts will change the input_select for you so you don’t need a separate automation to try to figure it out.

script:
  # Opens or closes the cover and sets the input select to the matching state
  cover_open_close:
    sequence:
      # TODO: Check the current state. Don't want to call 'close' if it's already closed. 
      #             If you do, it will go to shade position. 
      #             If it's already open, should be able to call 'open' again. It could be partially open...
      # First, attempt to call the cover command
      - service: "cover.{{'close' if action == 'chiuso' else 'open'}}_cover"
        data:
          entity_id: "{{ cover }}"
      # TODO: Use the closed sensor to tell if it worked? 
      - service: input_select.select_option
        entity_id: input_select.posizione_tapparella_camera
        data:
          option: "{{ action }}"
       # TODO: Do we need a delay here to wait for it to open/close?

  cover_set_position:
    # Input is a number from 0-100. Round it to the closest 0, 50, 100. 
    # If 0 or 100, just use the open/close script above. Easy. 
    # If 50, need to send a close command from closed position. 
    sequence:
      # Need to know the current state of the cover to know how to handle it. 
      - variables:
          # Capture the state we think the blinds are
          current_assumed_state: "{{states('input_select.posizione_tapparella_camera') }}"
          desired_state: >-
            {%- set rounded = 50 * ((( position | float)/50) | round) -%}
            {%- if rounded == 0 -%} 'chiuso'
            {%- elif rounded == 50 -%} 'tapparella'
            {%- else -%} 'aperto'
            {%- endif -%}
      # Template condition shorthand. If the desired state matches the current state, don't do anything. 
      # This is likely to happen using the slider as it might change value technically, but the rounded
      # value would be the same. 
      - "{{ current_assumed_state != desired_state }}"
      # This is a new state. Try to set it.
      - choose:
          # If this is an open/close command, just call that script above. 
          - conditions:
              - "{{ desired_state != 'tapparella'  }}"
            sequence:
              - service: script.cover_open_close
                data:
                  action: "{{desired_state}}"
                  cover: {{cover}}
            # If it wasn't open or close position, do this instead
            default:
              # To set this state, we have to close the cover first, then send another close command. 
              # If the cover isn't fully closed, send a closed command first. 
              - choose:
                  - conditions:
                       # Use our only sensor to know if the cover is closed. We could use the
                       # last input select state, but this should be safer. If this sensor is not on, 
                       # send a close command. 
                       - "{{ is_state('binary_sensor.cover_closed', 'on') }}
                    sequence:
                       - service: cover.close_cover
                         data:
                           entity_id: "{{ cover }}"
              # We either sent a closed command above, or it's already closed. 
              # In either case, don't continue until the cover says its closed. 
              # If it's already closed, this wont wait at all and will just move on. 
              - wait_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                timeout: "00:01:00"
                continue_on_timeout: false
              # If we got here, the cover closed. Send another close command. 
              - service: cover.close_cover
                data:
                  entity_id: "{{ cover }}"
              # Finally, update the input select
              - service: input_select.select_option
                entity_id: input_select.posizione_tapparella_camera
                data:
                  option: tapparella
      

Now we can use this script for the open/close template cover.

cover:
  - platform: template
    covers:
      tapparella_camera:
        device_class: shade    
        friendly_name: "Tapparella Camera"
        position_template: >
          {% if is_state('input_select.posizione_tapparella_camera', 'chiuso') %}
            0
          {% elif is_state('input_select.posizione_tapparella_camera', 'tapparella') %}
            50
          {% else %}
            100
          {% endif %}  
        open_cover:
          service: script.cover_open_close
          data:
            action: aperto
            cover: cover.bfdc1128af1eb05af1w4i6
        close_cover:
          service: script.cover_open_close
          data:
            action: chiuso
            cover: cover.bfdc1128af1eb05af1w4i6
        stop_cover:
          service: cover.stop_cover
          entity_id: cover.bfdc1128af1eb05af1w4i6
        set_cover_position:
          service: script.cover_set_position
          data:
            position: "{{position}}"
            cover: cover.bfdc1128af1eb05af1w4i6

I know this is a lot. And there will probably be syntax errors. Hope it helps!

1 Like

Wow this was a lot for you just to type it down ! Thanks man! I’ll give it a try and let you know!

So I tried the code and got few errors. First I had to comment link 19:

#- "{{ current_assumed_state != desired_state }}"

and line 39:

#- "{{ is_state('binary_sensor.cover_closed', 'on') }}

because they were both returning syntax errors. Also, this part here is returning an error:
image

I really wanted to troubleshoot it, but to be honest I’m not vary familiar with this syntax and no idea what to change… it’s pretty complex… But don’t want to bother you so I’ll try to figure it out over the next days :slight_smile:

Hmm, I thought I could use condition shorthand in an action sequence.

Also, the variables below assume you’re on 0.117 or higher (as does the template shorthand). If you’re not at that version, it would be easiest to move those variables to outside the script and pass them in as parameters rather than have to calculate them multiple times in the same script.

For example, you would call cover_set_position like this instead of just passing in position:

- service: script.cover_set_position
  data:
    position: "{{position}}"
    desired_state: >-
        {%- set rounded = 50 * ((( position | float)/50) | round) -%}
        {%- if rounded == 0 -%} 'chiuso'
        {%- elif rounded == 50 -%} 'tapparella'
        {%- else -%} 'aperto'
        {%- endif -%}
    current_assumed_state: "{{states('input_select.posizione_tapparella_camera') }}"
    cover: cover.bfdc1128af1eb05af1w4i6

If you do it that way, get rid of the -variables section. I’m wondering though why that section didn’t raise an error for you if you were on something before 0.117 though.

cover_set_position:
    # Input is a number from 0-100. Round it to the closest 0, 50, 100. 
    # If 0 or 100, just use the open/close script above. Easy. 
    # If 50, need to send a close command from closed position. 
    sequence:
      # Need to know the current state of the cover to know how to handle it. 
      - variables:
          # Capture the state we think the blinds are
          current_assumed_state: "{{states('input_select.posizione_tapparella_camera') }}"
          desired_state: >-
            {%- set rounded = 50 * ((( position | float)/50) | round) -%}
            {%- if rounded == 0 -%} 'chiuso'
            {%- elif rounded == 50 -%} 'tapparella'
            {%- else -%} 'aperto'
            {%- endif -%}
      # Template condition shorthand. If the desired state matches the current state, don't do anything. 
      # This is likely to happen using the slider as it might change value technically, but the rounded
      # value would be the same. 
      - condition: template
        value_template: "{{ current_assumed_state != desired_state }}"
      # This is a new state. Try to set it.
      - choose:
          # If this is an open/close command, just call that script above. 
          - conditions:
              - condition: template
                value_template: "{{ desired_state != 'tapparella'  }}"
            sequence:
              - service: script.cover_open_close
                data:
                  action: "{{desired_state}}"
                  cover: {{cover}}
            # If it wasn't open or close position, do this instead
            default:
              # To set this state, we have to close the cover first, then send another close command. 
              # If the cover isn't fully closed, send a closed command first. 
              - choose:
                  - conditions:
                       # Use our only sensor to know if the cover is closed. We could use the
                       # last input select state, but this should be safer. If this sensor is not on, 
                       # send a close command. 
                       - "{{ is_state('binary_sensor.cover_closed', 'on') }}
                    sequence:
                       - service: cover.close_cover
                         data:
                           entity_id: "{{ cover }}"
              # We either sent a closed command above, or it's already closed. 
              # In either case, don't continue until the cover says its closed. 
              # If it's already closed, this wont wait at all and will just move on. 
              - wait_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                timeout: "00:01:00"
                continue_on_timeout: false
              # If we got here, the cover closed. Send another close command. 
              - service: cover.close_cover
                data:
                  entity_id: "{{ cover }}"
              # Finally, update the input select
              - service: input_select.select_option
                entity_id: input_select.posizione_tapparella_camera
                data:
                  option: tapparella

As for that last error, the variable name should be ‘cover’, not ‘entity_id’

                data:
                  action: "{{desired_state}}"
                  cover: {{cover}}

Jim. I’m on 0.118.5. So based on that, if I understood correctly, the part I need is this:

cover_set_position:
    # Input is a number from 0-100. Round it to the closest 0, 50, 100. 
    # If 0 or 100, just use the open/close script above. Easy. 
    # If 50, need to send a close command from closed position. 
    sequence:
      # Need to know the current state of the cover to know how to handle it. 
      - variables:
          # Capture the state we think the blinds are
          current_assumed_state: "{{states('input_select.posizione_tapparella_camera') }}"
          desired_state: >-
            {%- set rounded = 50 * ((( position | float)/50) | round) -%}
            {%- if rounded == 0 -%} 'chiuso'
            {%- elif rounded == 50 -%} 'tapparella'
            {%- else -%} 'aperto'
            {%- endif -%}
      # Template condition shorthand. If the desired state matches the current state, don't do anything. 
      # This is likely to happen using the slider as it might change value technically, but the rounded
      # value would be the same. 
      - condition: template
        value_template: "{{ current_assumed_state != desired_state }}"
      # This is a new state. Try to set it.
      - choose:
          # If this is an open/close command, just call that script above. 
          - conditions:
              - condition: template
                value_template: "{{ desired_state != 'tapparella'  }}"
            sequence:
              - service: script.cover_open_close
                data:
                  action: "{{desired_state}}"
                  cover: {{cover}}
            # If it wasn't open or close position, do this instead
            default:
              # To set this state, we have to close the cover first, then send another close command. 
              # If the cover isn't fully closed, send a closed command first. 
              - choose:
                  - conditions:
                       # Use our only sensor to know if the cover is closed. We could use the
                       # last input select state, but this should be safer. If this sensor is not on, 
                       # send a close command. 
                       - "{{ is_state('binary_sensor.cover_closed', 'on') }}
                    sequence:
                       - service: cover.close_cover
                         data:
                           entity_id: "{{ cover }}"
              # We either sent a closed command above, or it's already closed. 
              # In either case, don't continue until the cover says its closed. 
              # If it's already closed, this wont wait at all and will just move on. 
              - wait_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                timeout: "00:01:00"
                continue_on_timeout: false
              # If we got here, the cover closed. Send another close command. 
              - service: cover.close_cover
                data:
                  entity_id: "{{ cover }}"
              # Finally, update the input select
              - service: input_select.select_option
                entity_id: input_select.posizione_tapparella_camera
                data:
                  option: tapparella

Unfortunately it’s still complaining about a syntax error. If I comment this line:

  • "{{ is_state(‘binary_sensor.cover_closed’, ‘on’) }}

it gets removed, otherwise it says:
image

sorry man If I’m bothering you, but again this syntax is really new to me… if you happen to have a link where it is well explained I’ll try to have a look myself too without bothering you every time

Oh, i missed that one too. I guess template shorthand conditions aren’t valid in choose either.

After the correction the syntax is ok, but now I get this:

Invalid config for [script]: [default] is an invalid option for [script]. Check: script->script->cover_set_position->sequence->2->choose->0->default. (See /config/configuration.yaml, line 20).

A missed quote, and another shorthand template. I’m on a roll!

          default:
              # To set this state, we have to close the cover first, then send another close command. 
              # If the cover isn't fully closed, send a closed command first. 
              - choose:
                  - conditions:
                       # Use our only sensor to know if the cover is closed. We could use the
                       # last input select state, but this should be safer. If this sensor is not on, 
                       # send a close command. 
                       - condition: template
                         value_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                    sequence:
                       - service: cover.close_cover
                         data:
                           entity_id: "{{ cover }}"

LOL !!!

I think I’m driving you crazy!
So this is the full “cover_set_position” script but I’m still getting the error “Invalid config for [script]: [default] is an invalid option for [script]. Check: script->script->cover_set_position->sequence->2->choose->0->default. (See /config/configuration.yaml, line 20)” when I restart HA :frowning:

cover_set_position:
    # Input is a number from 0-100. Round it to the closest 0, 50, 100. 
    # If 0 or 100, just use the open/close script above. Easy. 
    # If 50, need to send a close command from closed position. 
    sequence:
      # Need to know the current state of the cover to know how to handle it. 
      - variables:
          # Capture the state we think the blinds are
          current_assumed_state: "{{states('input_select.posizione_tapparella_camera') }}"
          desired_state: >-
            {%- set rounded = 50 * ((( position | float)/50) | round) -%}
            {%- if rounded == 0 -%} 'chiuso'
            {%- elif rounded == 50 -%} 'tapparella'
            {%- else -%} 'aperto'
            {%- endif -%}
      # Template condition shorthand. If the desired state matches the current state, don't do anything. 
      # This is likely to happen using the slider as it might change value technically, but the rounded
      # value would be the same. 
      - condition: template
        value_template: "{{ current_assumed_state != desired_state }}"
      # This is a new state. Try to set it.
      - choose:
          # If this is an open/close command, just call that script above. 
          - conditions:
              - condition: template
                value_template: "{{ desired_state != 'tapparella'  }}"
            sequence:
              - service: script.cover_open_close
                data:
                  action: "{{ desired_state }}"
                  cover: "{{ cover }}"
            # If it wasn't open or close position, do this instead
            default:
              # To set this state, we have to close the cover first, then send another close command. 
              # If the cover isn't fully closed, send a closed command first. 
              - choose:
                  - conditions:
                       # Use our only sensor to know if the cover is closed. We could use the
                       # last input select state, but this should be safer. If this sensor is not on, 
                       # send a close command. 
                       - condition: template
                         value_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                    sequence:
                       - service: cover.close_cover
                         data:
                           entity_id: "{{ cover }}"
              # We either sent a closed command above, or it's already closed. 
              # In either case, don't continue until the cover says its closed. 
              # If it's already closed, this wont wait at all and will just move on. 
              - wait_template: "{{ is_state('binary_sensor.cover_closed', 'on') }}"
                timeout: "00:01:00"
                continue_on_timeout: false
              # If we got here, the cover closed. Send another close command. 
              - service: cover.close_cover
                data:
                  entity_id: "{{ cover }}"
              # Finally, update the input select
              - service: input_select.select_option
                entity_id: input_select.posizione_tapparella_camera
                data:
                  option: tapparella

Nope, not at all. It’s my fault for trying to write all of this in the forum rather than VSCode and copy/paste it here. Hard to see indentations.

For choose, the options are a list of (conditions/sequence) and a single default. In my example, the default is indented too far so it’s part of the list of (conditions/sequence) rather than a child of ‘choose’.

And I also figured out why the shorthand wasn’t working. It’s slightly different. Don’t need to change those if you don’t want.

Instead of

  - conditions:
    - "{{ shorthand notation }}"

It’s just

  - conditions: "{{ shorthand notation }}"

Anyway, move the default section to be the same indentation level as the word choose.

- choose:
      # If this is an open/close command, just call that script above. 
      - conditions: "{{ desired_state != 'tapparella' }}"
        sequence:
          - service: script.cover_open_close
            data:
              action: "{{ desired_state }}"
              cover: "{{ cover }}"
  # Shift this entire thing over to be the same indentation as 'choose' above. 
  default:
    # Also shift these things over relatively...
    - choose
   ...

No worries, I believe you’re already doing so much to help me out… :slight_smile:
So, it seems that it’s now ok ! I’ll give it a try and change things where I have to.
For now I’d like to thank you so much for your help (for sure I’ll need more :smiley: )

So, I made few tests and it seems the scripts work fine, but now I have another issue :slight_smile:

Let me first tell that I have an automation that will stop the cover if the magnetic sensor will change its status:

alias: Tapparelle
description: ''
trigger:
  - platform: state
    entity_id: binary_sensor.stop_finestracamera
    from: 'on'
    to: 'off'
condition: []
action:
  - service: cover.stop_cover
    data: {}
    entity_id: cover.bfdc1128af1eb05af1w4i6
mode: single

Basically if I close the cover from a “full open” situation, it goes down and stops by the magnetic sensor, but not enough to keep it as “closed” (it passes just a little bit over so the sensor returns to “open” status). After this, the input variable is set to close but I’m unable to put the cover in blinds mode:

image

as I cannot close it again. Additionally, the cover doesn’t update its status if it’s open manually from the wall switch. For this I have to build another automation I guess.