-
-
Save daniel-sc/074465be04dd9a793cba5b841ab63f83 to your computer and use it in GitHub Desktop.
| # see https://community.home-assistant.io/t/close-open-curtain-cover-blinds-based-on-sun-and-weather/584240 | |
| blueprint: | |
| name: cover sun | |
| description: Close cover when sun is shining and open when stops. This considers weather (sunny, partly cloudy), sun position (elevation, azimuth) and temperature. | |
| domain: automation | |
| source_url: https://gist.github.com/daniel-sc/074465be04dd9a793cba5b841ab63f83 | |
| input: | |
| cover_entity: | |
| name: cover | |
| selector: | |
| entity: | |
| domain: cover | |
| sun_position_start: | |
| name: Sun azimuth position start | |
| description: Sun azimuth. When sun is over this value, shutter could closed. | |
| default: 89 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 360 | |
| unit_of_measurement: ° | |
| sun_position_end: | |
| name: Sun azimuth position end | |
| description: Sun azimuth. when sun is over this value, shutter should open. | |
| default: 200 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 360 | |
| unit_of_measurement: ° | |
| sun_elevation_start: | |
| name: Sun elevation to start | |
| description: Sun elevation. When sun is over this value, shutter could be closed. | |
| default: 32 | |
| selector: | |
| number: | |
| min: -100 | |
| max: 360 | |
| unit_of_measurement: ° | |
| outdoor_temp: | |
| name: Temperature threshold | |
| description: Temperature above which the shutters will close. Based on Temperature source (see below). | |
| default: 16 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 30 | |
| unit_of_measurement: °C | |
| close_position: | |
| name: Closing position of curtain | |
| description: "Target position for closed curtain. Note: 100 is open!" | |
| default: 30 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 99 | |
| no_open_after: | |
| name: Night time begin | |
| description: > | |
| Any open/close action that would happen during the configured night time is ignored. | |
| The automation does not re-evaluate automatically at night time end. | |
| By default, night time handling is disabled because night time begin and end are the same value. | |
| default: "00:00:00" | |
| selector: | |
| time: | |
| no_open_before: | |
| name: Night time end | |
| description: > | |
| Any open/close action that would happen during the configured night time is ignored. | |
| The automation does not re-evaluate automatically at night time end. | |
| By default, night time handling is disabled because night time begin and end are the same value. | |
| default: "00:00:00" | |
| selector: | |
| time: | |
| random_delay_max: | |
| name: Random command delay max | |
| description: Randomly delay cover commands between 0 and this many seconds. This can help avoid simultaneous Zigbee commands when multiple automations trigger at the same time. Set to 0 to disable. | |
| default: 30 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: seconds | |
| weather: | |
| name: Weather service | |
| default: "weather.forecast_home" | |
| selector: | |
| entity: | |
| domain: weather | |
| temperature_entity: | |
| name: Temperature source | |
| default: "weather.forecast_home" | |
| selector: | |
| entity: | |
| filter: | |
| - domain: | |
| - weather | |
| - device_class: | |
| - temperature | |
| close_weather_conditions: | |
| name: Weather conditions | |
| description: Close curtain on all of the selected weather conditions (if all other conditions are met) | |
| default: | |
| - sunny | |
| - partlycloudy | |
| selector: | |
| select: | |
| multiple: true | |
| options: | |
| - "clear-night" | |
| - "cloudy" | |
| - "fog" | |
| - "hail" | |
| - "lightning" | |
| - "lightning-rainy" | |
| - "partlycloudy" | |
| - "pouring" | |
| - "rainy" | |
| - "snowy" | |
| - "snowy-rainy" | |
| - "sunny" | |
| - "windy" | |
| - "windy-variant" | |
| - "exceptional" | |
| variables: | |
| sun_elevation_close: !input sun_elevation_start | |
| sun_azimuth_close: !input sun_position_start | |
| sun_azimuth_open: !input sun_position_end | |
| temp_close: !input outdoor_temp | |
| curtain_entity_id: !input cover_entity | |
| curtain_target_position: !input close_position | |
| no_open_after_time: !input no_open_after | |
| no_open_before_time: !input no_open_before | |
| random_delay_max_seconds: !input random_delay_max | |
| is_night: "{{ no_open_after_time is not none and no_open_before_time is not none and (( today_at(no_open_after_time) < now() < today_at(no_open_before_time)) or (today_at(no_open_after_time) > today_at(no_open_before_time) and (now() > today_at(no_open_after_time) or now() < today_at(no_open_before_time)))) }}" | |
| weather_entity_id: !input weather | |
| temperature_entity_id: !input temperature_entity | |
| weather_close: !input close_weather_conditions | |
| curtain_current_position: "{{ state_attr(curtain_entity_id, 'current_position') if state_attr(curtain_entity_id, 'current_position') is not none else state_attr(curtain_entity_id, 'position') }}" | |
| current_temp: > | |
| {% set t = state_attr(temperature_entity_id, 'temperature') %} | |
| {{ (t if t is not none else states(temperature_entity_id)) | float(none) }} | |
| mode: queued # We rely on trigger changes, so 'single' and 'restart' are not possible. 'parallel' might work. | |
| trigger: | |
| - platform: numeric_state | |
| entity_id: sun.sun | |
| attribute: elevation | |
| above: !input sun_elevation_start | |
| id: t_sun_elevation_above | |
| - platform: numeric_state | |
| entity_id: sun.sun | |
| attribute: elevation | |
| below: !input sun_elevation_start | |
| id: t_sun_elevation_below | |
| - platform: numeric_state | |
| entity_id: sun.sun | |
| attribute: azimuth | |
| above: !input sun_position_start | |
| id: t_sun_azimuth_start_above | |
| - platform: numeric_state | |
| entity_id: sun.sun | |
| attribute: azimuth | |
| above: !input sun_position_end | |
| id: t_sun_azimuth_end_above | |
| - platform: state | |
| entity_id: !input weather | |
| to: !input close_weather_conditions | |
| not_from: !input close_weather_conditions | |
| id: t_weather_sunny | |
| - platform: state | |
| entity_id: !input weather | |
| not_to: !input close_weather_conditions | |
| from: !input close_weather_conditions | |
| id: t_weather_not_sunny | |
| # temperature_entity can either be: | |
| # - a weather entity, where temperature is an attribute | |
| # - a sensor entity, where temperature is the state | |
| - platform: numeric_state | |
| entity_id: !input temperature_entity | |
| value_template: > | |
| {{ state.attributes.get('temperature', state.state) | float(none) }} | |
| above: !input outdoor_temp | |
| id: t_temp_above | |
| - platform: numeric_state | |
| entity_id: !input temperature_entity | |
| value_template: > | |
| {{ state.attributes.get('temperature', state.state) | float(none) }} | |
| below: !input outdoor_temp | |
| id: t_temp_below | |
| condition: | |
| # During night time, triggers are ignored | |
| - condition: template | |
| value_template: "{{ not is_night }}" | |
| - condition: or | |
| conditions: | |
| # do close when current cover position is above curtain_target_position | |
| - condition: and | |
| conditions: | |
| - condition: template | |
| value_template: "{{ curtain_current_position > curtain_target_position }}" | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) in weather_close }}" | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}" | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}" | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp >= temp_close }}" | |
| # do open (when it (first) leaves the closed conditions (prevent e.g. opening at night when any trigger fires)): | |
| - condition: and | |
| conditions: | |
| - condition: template | |
| value_template: "{{ curtain_current_position <= curtain_target_position }}" | |
| - condition: or | |
| conditions: | |
| - condition: and | |
| conditions: | |
| - condition: trigger | |
| id: t_weather_not_sunny | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}" | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}" | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp >= temp_close }}" | |
| - condition: and | |
| conditions: | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) in weather_close }}" | |
| - condition: trigger | |
| id: t_sun_elevation_below | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}" | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp >= temp_close }}" | |
| - condition: and | |
| conditions: | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) in weather_close }}" | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}" | |
| - condition: trigger | |
| id: t_sun_azimuth_end_above | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp >= temp_close }}" | |
| - condition: and | |
| conditions: | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) in weather_close }}" | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}" | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}" | |
| - condition: trigger | |
| id: t_temp_below | |
| action: | |
| - choose: | |
| # close cover | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ curtain_current_position > curtain_target_position }}" | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) in weather_close }}" | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}" | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}" | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp >= temp_close }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ range(0, random_delay_max_seconds | int + 1) | random }}" | |
| - service: cover.set_cover_position | |
| data: | |
| position: "{{ curtain_target_position }}" | |
| target: | |
| entity_id: "{{ curtain_entity_id }}" | |
| # open cover (conditions are somewhat redundant here, as 'condition' is more specific..) | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ curtain_current_position <= curtain_target_position }}" | |
| - condition: or | |
| conditions: | |
| - condition: template | |
| value_template: "{{ states(weather_entity_id) not in weather_close }}" | |
| - condition: template | |
| value_template: "{{ state_attr('sun.sun', 'elevation') < sun_elevation_close }}" | |
| - condition: template | |
| value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and (sun_azimuth_close > state_attr('sun.sun', 'azimuth') or state_attr('sun.sun', 'azimuth') > sun_azimuth_open)) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') < sun_azimuth_close and state_attr('sun.sun', 'azimuth') > sun_azimuth_open)) }}" | |
| - condition: template | |
| value_template: "{{ current_temp is number and current_temp < temp_close }}" | |
| sequence: | |
| - delay: | |
| seconds: "{{ range(0, random_delay_max_seconds | int + 1) | random }}" | |
| - service: cover.set_cover_position | |
| data: | |
| position: 100 | |
| target: | |
| entity_id: "{{ curtain_entity_id }}" |
Please see https://community.home-assistant.io/t/close-open-curtain-cover-blinds-based-on-sun-and-weather/584240 for discussions an known issues/limitations.
@riddik14 this was a warning because the blueprint did not cleanly handle variance between weather/temperature entity - this should be fixed with the latest version.
Hi @daniel-sc. Love your blueprint! But I'm having some problems lately...
Bug: the "open" root-gate condition is edge-triggered and can permanently miss its window
I've been debugging why my shading covers (using this blueprint) stay closed long after the sun has clearly moved past the shading window (azimuth well beyond sun_position_end, elevation and temperature both still high). Traced it down to a real logic issue.
There are two separate places that decide whether to open:
The action itself (second choose branch) correctly checks live state: weather no longer in weather_close OR elevation < sun_elevation_close OR azimuth outside the [sun_position_start, sun_position_end] window OR temp < temp_close. This is state-based and would evaluate correctly at any time.
The root condition: block that gates whether the automation proceeds to the action at all repeats the same four checks, but each one is additionally tied to condition: trigger / id: <specific_trigger> (t_weather_not_sunny, t_sun_elevation_below, t_sun_azimuth_end_above, t_temp_below). Since these are numeric_state/state triggers, they only fire once, at the exact instant a threshold is crossed. If any of the other three sub-conditions isn't simultaneously true at that precise instant, the whole automation short-circuits with failed_conditions — and since the triggering event was a one-time edge, that opportunity is gone until some other trigger happens to align with all four conditions at once.
In my case: sun_position_end was crossed hours ago, and weather/temperature/elevation have stayed well within "should be open" territory ever since, but the automation logged several failed_conditions runs (triggered by unrelated weather-entity updates) instead of opening, because the root gate never got re-evaluated in a way that matched its rigid trigger-id requirement.
Fix: replace the four trigger-id-gated and blocks in the root gate's "open" OR-list with the same plain state-based checks the action already uses correctly. This makes the gate re-evaluate correctly on any of the existing triggers, instead of requiring one very specific edge to coincide with all conditions.
--- cover_sun_blueprint.yaml (original)
+++ cover_sun_blueprint.yaml (fix)
@@ inside the "do open" OR-block of the root condition @@
- condition: and
conditions:
- condition: template
value_template: "{{ curtain_current_position <= curtain_target_position }}"
- condition: or
conditions:
- - condition: and
- conditions:
- - condition: trigger
- id: t_weather_not_sunny
- - condition: template
- value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}"
- - condition: template
- value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}"
- - condition: template
- value_template: "{{ current_temp is number and current_temp >= temp_close }}"
-
- - condition: and
- conditions:
- - condition: template
- value_template: "{{ states(weather_entity_id) in weather_close }}"
- - condition: trigger
- id: t_sun_elevation_below
- - condition: template
- value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}"
- - condition: template
- value_template: "{{ current_temp is number and current_temp >= temp_close }}"
-
- - condition: and
- conditions:
- - condition: template
- value_template: "{{ states(weather_entity_id) in weather_close }}"
- - condition: template
- value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}"
- - condition: trigger
- id: t_sun_azimuth_end_above
- - condition: template
- value_template: "{{ current_temp is number and current_temp >= temp_close }}"
-
- - condition: and
- conditions:
- - condition: template
- value_template: "{{ states(weather_entity_id) in weather_close }}"
- - condition: template
- value_template: "{{ state_attr('sun.sun', 'elevation') >= sun_elevation_close }}"
- - condition: template
- value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and sun_azimuth_close <= state_attr('sun.sun', 'azimuth') <= sun_azimuth_open) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') >= sun_azimuth_close or state_attr('sun.sun', 'azimuth') <= sun_azimuth_open)) }}"
- - condition: trigger
- id: t_temp_below
+ # Same checks the action itself already uses - plain state
+ # based, not tied to one specific trigger id. Any of the
+ # existing triggers can now re-evaluate this correctly,
+ # instead of requiring one exact edge to coincide with
+ # all four conditions at once.
+ - condition: template
+ value_template: "{{ states(weather_entity_id) not in weather_close }}"
+ - condition: template
+ value_template: "{{ state_attr('sun.sun', 'elevation') < sun_elevation_close }}"
+ - condition: template
+ value_template: "{{ (sun_azimuth_close <= sun_azimuth_open and (sun_azimuth_close > state_attr('sun.sun', 'azimuth') or state_attr('sun.sun', 'azimuth') > sun_azimuth_open)) or (sun_azimuth_close > sun_azimuth_open and (state_attr('sun.sun', 'azimuth') < sun_azimuth_close and state_attr('sun.sun', 'azimuth') > sun_azimuth_open)) }}"
+ - condition: template
+ value_template: "{{ current_temp is number and current_temp < temp_close }}"I've verified this against a live instance: with the current fix, the condition evaluates true immediately when azimuth/elevation/temp/weather actually call for opening, rather than waiting for a coincidental exact-edge match.
Happy to test a PR if you'd like to take a different approach to fixing it.
Logger: homeassistant.components.homeassistant.triggers.numeric_state
Source: components/homeassistant/triggers/numeric_state.py:200
Integration: Home Assistant Core Integration (documentation, issues)
First occurred: 9 febbraio 2024 alle ore 23:03:01 (20 occurrences)
Last logged: 08:23:01
Error in 'cover sun auto' trigger: In 'numeric_state' condition: entity weather.forecast_ home state 'rainy' cannot be processed as a number
Error in 'cover sun auto' trigger: In 'numeric_state' condition: entity weather.forecast_home state 'cloudy' cannot be processed as a number