-
-
Save lukasvice/b364724d84c3ac4e160f7a7d8fa37066 to your computer and use it in GitHub Desktop.
# 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 |
Hi @lukasvice!
This is a nice script.
But what does the following addition you posted do?
If I understand correctly, you save the last position and the tilt position, but for what? :-)
Why I ask: It would be nice to control the position and tilt position separately.
For example, if I move the blind to position 50 and tilt it to 50 and then move it to position 70 without tilt, it would be nice to remember the last tilt and tilt it back to 50.
Is this possible?
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
andclose
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 }}
Hi @maxschloegl, I save the last position/tilt so that the blinds do not move when I request the same position/tilt again. This is useful if you run automations that want to move the blinds even if they are already in that position. I see what you're trying to do, and yes, you could use the saved tilt position if no tilt is provided as a parameter, so it wouldn't change. Personally, I have not come across this use case yet.
Hi everyone, Shelly has announced the new 2PM Gen3 with drum roll blind angle control! Let's hope they got it right and this is the feature we've been waiting for!
Hi everyone, Shelly has announced the new 2PM Gen3 with drum roll blind angle control! Let's hope they got it right and this is the feature we've been waiting for!
It actually is. I raised a ticket with them a few weeks ago where they told me that i should stay tuned for this feature to come. The 3rd Gen Pro devices already have that feature. Now HomeAssistant integration needs an update I guess and than things should work.
Hi. Nice script. I'm trying to make an action where at a specified time 4 blinds (4 x Shelly 2PM) are set to a certain position and no way I can get it to work. Only maybe 2 of the 4 always respond. Or some don't set the tilt position. It doesn't even work when I enter actions in succession or in parallel. If I make an action on just one blinds, it works.
Any idea?
I had the exact same issue and your hint solved it. Thanks!
Just for curiosity: Does anyone have an idea, why this is happening? I am running HA in Docker on a Synology NAS.
BR Tim