Complex blind automation

Hi all,

I’m trying to achieve, by what I think, a complex blind automation based on Google Calendar event AND/OR time.

I would like to open the blinds automation in our bedrooms based on day and time.
I have 3 bedrooms, but for now I want just for 1 bedroom the automation.

What should the automation do?

  1. Check Google Calendar for events with the name “Zomervakantie” (Summer holiday), Herstvakantie (autumn break), “Eerste Kerstdag” (Christmas Day), when 1 of these events is in the calendar then the blind should go open at 10.30 AM
  2. If no Google calendar event, then check weekday for monday til friday and open blinds at 07.30 AM
  3. If no Google calendar event, then check weekend saturday and sunday open blinds at 10.30 AM
  4. for number 2 and 3 should be checked sun.sun “sun above horizon”, if sun NOT above horizon then wait to be above horizon (mainly in winter).

So far I have this automation, but it doesn’t work :frowning: :slight_smile:

alias: Rolluik Sophie open
description: Rolluik Sophie open op basis van Google Agenda event, dag en tijd.
trigger:
  - platform: template
    value_template: >
      {% set event_messages = ['Zomervakantie', 'Herfstvakantie',
      'Kerstvakantie', 'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag Sophie',
      'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag', 'Eerste
      Pinksterdag', 'Tweede Pinksterdag'] %} {% set calendar_event =
      states.calendar.CALENDARNAME.attributes %} {% if calendar_event
      and calendar_event.start_time %}
        {% set start_time = as_timestamp(calendar_event.start_time) %}
        {% set start_date = start_time | timestamp_custom('%Y-%m-%d', true) %}
        {% set today_date = now().date() %}
        {{ calendar_event.message in event_messages and start_date == today_date }}
      {% else %}
        {{ false }}
      {% endif %}
  - platform: time
    at: "07:30:00"
  - platform: time
    at: "10:30:00"
condition: []
action:
  - choose:
      - conditions:
          - condition: template
            value_template: >
              {% set event_messages = ['Zomervakantie', 'Herfstvakantie',
              'Kerstvakantie', 'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag
              Sophie', 'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag',
              'Eerste Pinksterdag', 'Tweede Pinksterdag'] %} {% set
              calendar_event =
              states.calendar.CALENDARNAME.attributes %} {% if
              calendar_event and calendar_event.start_time %}
                {% set start_time = as_timestamp(calendar_event.start_time) %}
                {% set start_date = start_time | timestamp_custom('%Y-%m-%d', true) %}
                {% set today_date = now().date() %}
                {{ calendar_event.message in event_messages and start_date == today_date }}
              {% else %}
                {{ false }}
              {% endif %}
          - condition: time
            before: "10:30:00"
            after: "10:29:00"
        sequence:
          - service: cover.open_cover
            data: {}
            target:
              entity_id: cover.rolluik_sophie_2
      - conditions:
          - condition: time
            before: "07:31:00"
            after: "07:29:00"
            weekday:
              - mon
              - tue
              - wed
              - thu
              - fri
        sequence:
          - choose:
              - conditions:
                  - condition: sun
                    before: sunset
                    after: sunrise
                sequence:
                  - wait_template: "{{ is_state('sun.sun', 'above_horizon') }}"
                  - service: cover.open_cover
                    data: {}
                    target:
                      entity_id: cover.rolluik_sophie_2
          - service: cover.open_cover
            data: {}
            target:
              entity_id: cover.rolluik_sophie_2
      - conditions:
          - condition: time
            before: "10:31:00"
            weekday:
              - sat
              - sun
            after: "10:29:00"
        sequence:
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ is_state('sun.sun', 'above_horizon') }}"
                sequence:
                  - service: cover.open_cover
                    data: {}
                    target:
                      entity_id: cover.rolluik_sophie_2
          - service: cover.open_cover
            data: {}
            target:
              entity_id: cover.rolluik_sophie_2
mode: parallel

In the weekend the blind opend at 07.30 AM, today summerholiday event in Google Calendar it didn’t open at all.

There are a couple possible issues with the automation:

  1. If this calendar will ever have overlapping or coincident events, you cannot reliably use the state of the calendar entity. In that case, you will need to use the calendar.get_events service call.

  2. The method you are using in the template to get start_date returns a string, which you are comparing to a date object which will always be false. Instead, you need to compare similar data types:

...
  {% set start_date = (calendar_event.start_time|as_datetime|as_local).date() %}
  {% set today_date = now().date() %}
...

However, the Template trigger is very likely unneeded and should be removed. If your events are all-day events, the Template trigger is going to fire immediately after midnight… which won’t pass any of your condition sets. If your events are not all-day and do start at 7:30 or 10:30, they won’t pass any of your condition sets. So, the template trigger will likely never lead to an action, and the 2 cases where it would are already covered by the time triggers.

Automation configuration (If your calendar has overlapping events this will not solve the problem)
alias: Rolluik Sophie open
description: Rolluik Sophie open op basis van Google Agenda event, dag en tijd.
trigger:
  - platform: time
    at: "07:30:00"
    id: normal
  - platform: time
    at: "10:30:00"
    id: late
  - platform: sun
    event: sunrise
    id: normal
condition:
  - or:
      - and:
          - condition: trigger
            id: late
          - or:
              - condition: template
                value_template: >
                  {% set event_messages = ['Zomervakantie', 'Herfstvakantie',
                  'Kerstvakantie', 'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag
                  Sophie', 'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag',
                  'Eerste Pinksterdag', 'Tweede Pinksterdag'] %}
                  {% set calendar_event = states.calendar.CALENDARNAME.attributes %}
                  {% if calendar_event.start_time is defined %}              
                    {% set start_date = (calendar_event.start_time|as_datetime|as_local).date() %}
                    {% set today_date = now().date() %}
                    {{ calendar_event.message in event_messages and start_date == today_date }}
                  {% else %}
                    false
                  {% endif %}
                - condition: time
                  weekday:
                    - sat
                    - sun
      - and:
          - condition: trigger
            id: normal        
          - condition: sun
            after: sunrise
          - condition: time
            after: "07:29:00"
            weekday:
              - mon
              - tue
              - wed
              - thu
              - fri
action:
  - service: cover.open_cover
    data: {}
    target:
      entity_id: cover.rolluik_sophie_2
mode: queued

Thanks for the repley.

Indeed I have overlapping events.
Since the trigger is “Holiday” the event is all-day.

So the automation I want to create is not possible?
I will have a look at get_events.

I THINK I got it working, still testing though.

Step 1: I created an input boolean (switch) for each room
Step 2: I created an automation which checks the Google Calendar event and IF event is active then the input boolean is set to “On” IF NOT then input boolean is set to “Off”
Step 3: I created an automation which checks every 5 minutes the state of the input boolean (on or off). In case the input boolean state is “On” AND the sun is above horizon then opens blind at 10.30.
In the same automation when the boolean state is “off” AND day is week AND sun is above horizon then opens blind at 07.30, if sun is not above horizon then wait until sun is above horizon and open blind, the 3thrd automation is when the boolean state is “off” AND day is weekend AND sun is above horizon then opens blind at 10.30, if sun is not above horizon then wait until sun is above horizon and open blind.

Tomorrow I will hopefully know if this works :), then I will share the yaml code.

Do your rooms celebrate different holidays? :upside_down_face:

I would suggest setting up a trigger-based template binary sensor to perform the calendar.get_events service and store the result instead of an automation and an input boolean.

There’s no reason to check every 5 minutes… in Home Assistant, continuous polling using the Time Pattern trigger is rarely the correct method to trigger an automation. The 3 events that should be your triggers have not changed from what I propose above… the only thing that has changed is the method you use to find whether it it’s a holiday.

The one thing to be aware of is to make sure that the calendar is checked earlier than the earliest sunrise in your location. Based on the holidays you listed, that means before 05:00, since the earliest annual sunrise in the Netherlands is usually around 05:02.

LOL, no my rooms don’t celebrate holiday’s but the person who lives in that room does :wink:

The trigger based template is the thing I can’t get to work.

You are true about the 5 minute timer. I could rebuild that to 04.30 AM (just to be sure) and since these are all-day events they don’t change during the day.

Hopefully I can an update tomorrow if this automation succeeded.

The exact format depends on what your goal for the sensor is… This is probably how I would approach it for your use:

template:
  - trigger:
      - platform: time
        at: "01:00:00"
    action:
      - service: calendar.get_events
        data:
          start_date_time: "{{ now() }}"
          end_date_time: "{{ today_at() + timedelta(days=1, minutes= -1) }}"
        target:
          entity_id: calendar.YOUR_CALENDAR
        response_variable: agenda
      - variables:
          event_messages:
            - Zomervakantie
            - Herfstvakantie
            - Kerstvakantie
            - Eerste Kerstdag
            - Tweede Kerstdag
            - Studiedag Sophie
            - Eerste Paasdag
            - Tweede Paasdag
            - Hemelvaartsdag
            - Eerste Pinksterdag
            - Tweede Pinksterdag
          calendar_event_title: |
            {{ agenda['calendar.YOUR_CALENDAR']['events']
            | selectattr('summary', 'in', event_messages) | map(attribute='summary') | join }}
    binary_sensor:
      - name: Is it a Holiday
        state: "{{ calendar_event_title != '' }}"
        attributes:
          holiday_title: "{{ calendar_event_title if calendar_event_title != '' else 'No Holiday' }}"

This gives you a binary sensor whose state you can use as a general condition (for rooms that celebrate all the holidays :laughing: ) and the holiday_title attribute can be used to compare against room-specific lists when needed.

Thanks a lot, going to try this!

My automation didn’t go completely well… I forgot in the automation to check the boolean state so the blinds opend at 07.30 AM this morning and the boolean was “On”.

Since this morning the automation works like I wanted to.
Blinds open depending on Calendar event, weekdays and time.
I created the steps below for each room.

Still 3 steps:

  1. Created an input boolean (switch) per room
  2. Created an automation which checks Google Calendar event and IF event is active then the input boolean is to “On” else to “Off”
    Code:
alias: Update Event ROOM Boolean
description: >-
  Update input.boolean event_ROOM to "On" or "Off" based on Google
  Calendar events
trigger:
  - platform: time_pattern
    minutes: "30"
    hours: "4"
condition:
  - condition: template
    value_template: >
      {% set event_messages = [
        'Zomervakantie', 'Herfstvakantie', 'Kerstvakantie',
        'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag',
        'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag',
        'Eerste Pinksterdag', 'Tweede Pinksterdag'
      ] %} {% set calendar_event =
      state_attr('calendar.CALENDARNAME', 'attributes') %} {% if
      calendar_event and calendar_event.start_time %}
        {% set start_time = as_timestamp(calendar_event.start_time) %}
        {% set start_date = start_time | timestamp_custom('%Y-%m-%d', true) %}
        {% set today_date = now().date() %}
        {{ calendar_event.message in event_messages and start_date == today_date }}
      {% else %}
        {{ false }}
      {% endif %}
action:
  - target:
      entity_id: input_boolean.event_ROOM
    action: |
      {% if is_state('calendar.CALENDARNAME', 'on') %}
        input_boolean.turn_on
      {% else %}
        input_boolean.turn_off
      {% endif %}
mode: single

3.I created an automation which checks the state of the input boolean (on or off). In case the input boolean state is “On” AND the sun is above horizon then opens blind at 10.30.
In the same automation when the boolean state is “off” AND day is week AND sun is above horizon then opens blind at 07.30, if sun is not above horizon then wait until sun is above horizon and open blind, the 3thrd automation is when the boolean state is “off” AND day is weekend AND sun is above horizon then opens blind at 10.30, if sun is not above horizon then wait until sun is above horizon and open blind.

The code I used:

alias: Blinds ROOM open
description: Blinds ROOM open based on Google Calendar event, day and time.
trigger:
  - platform: time
    at: "07:30:00"
condition: []
action:
  - choose:
      - conditions:
          - condition: state
            entity_id: input_boolean.event_ROOM
            state: "on"
        sequence:
          - delay: "03:00:00"
          - choose:
              - conditions:
                  - condition: sun
                    after: sunrise
                sequence:
                  - target:
                      entity_id: cover.blinds_ROOM
                    action: cover.open_cover
                    data: {}
              - conditions:
                  - condition: sun
                    before: sunrise
                sequence:
                  - wait_template: "{{ is_state('sun.sun', 'above_horizon') }}"
                    timeout: "03:00:00"
                  - target:
                      entity_id: cover.blinds_ROOM
                    action: cover.open_cover
                    data: {}
      - conditions:
          - condition: state
            entity_id: input_boolean.event_ROOM
            state: "off"
          - condition: time
            weekday:
              - mon
              - tue
              - wed
              - thu
              - fri
        sequence:
          - choose:
              - conditions:
                  - condition: sun
                    after: sunrise
                sequence:
                  - target:
                      entity_id: cover.blind_ROOM
                    action: cover.open_cover
                    data: {}
              - conditions:
                  - condition: sun
                    before: sunrise
                sequence:
                  - wait_template: "{{ is_state('sun.sun', 'above_horizon') }}"
                    timeout: "03:00:00"
                  - target:
                      entity_id: cover.blind_ROOM
                    action: cover.open_cover
                    data: {}
      - conditions:
          - condition: state
            entity_id: input_boolean.event_ROOM
            state: "off"
          - condition: time
            weekday:
              - sat
              - sun
        sequence:
          - delay: "03:00:00"
          - choose:
              - conditions:
                  - condition: sun
                    after: sunrise
                sequence:
                  - target:
                      entity_id: cover.blind_ROOM
                    action: cover.open_cover
                    data: {}
              - conditions:
                  - condition: sun
                    before: sunrise
                sequence:
                  - wait_template: "{{ is_state('sun.sun', 'above_horizon') }}"
                    timeout: "03:00:00"
                  - target:
                      entity_id: cover.blind_ROOM
                    action: cover.open_cover
                    data: {}
mode: single

If the condition block can only be passed on days that are holidays, when will the Input boolean ever be turned off by the automation? Do you have another automation to turn off the booleans?

Just be aware that waits and delays do not survive restart or reload, so it is generally not advisable to use prolonged waits in automations. The preferred method is to use multiple triggers, as shown in my examples above.

I thought this part of the code selected the state of the switch.

So this doesn’t?

Does this code do the trick?:

trigger:
  - platform: time_pattern
    minutes: "30"
    hours: "4"
condition:
  - condition: template
    value_template: >
      {% set event_messages = [
        'Zomervakantie', 'Herfstvakantie', 'Kerstvakantie',
        'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag',
        'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag',
        'Eerste Pinksterdag', 'Tweede Pinksterdag'
      ] %} 
      {% set calendar_event = state_attr('calendar.CALENDARNAME', 'start_time') %}
      {% set event_message = state_attr('calendar.CALENDARNAME', 'message') %}
      {% if calendar_event %}
        {% set start_time = as_timestamp(calendar_event) %}
        {% set start_date = start_time | timestamp_custom('%Y-%m-%d', true) %}
        {% set today_date = now().date() %}
        {{ event_message in event_messages and start_date == today_date }}
      {% else %}
        {{ false }}
      {% endif %}
action:
  - service: input_boolean.turn_{{ 'on' if condition[0].value_template else 'off' }}
    target:
      entity_id: input_boolean.event_ROOM
mode: single

I.r.t. number 3: Good point, didn’t know that, but now you are telling this it makes complete sence…

That part will not be executed on any day that is not a holiday, because of the Condition/“And If” block.

If you want it to handle both actions, you need to move the template out of the Condition block and use it to set a variable:

trigger:
  - platform: time_pattern
    minutes: "30"
    hours: "4"
condition: []
action:
  - variables:
      holiday: |
        {% set event_messages = [
        'Zomervakantie', 'Herfstvakantie', 'Kerstvakantie',
        'Eerste Kerstdag', 'Tweede Kerstdag', 'Studiedag',
        'Eerste Paasdag', 'Tweede Paasdag', 'Hemelvaartsdag',
        'Eerste Pinksterdag', 'Tweede Pinksterdag'
        ] %} 
        {% set calendar_event = state_attr('calendar.CALENDARNAME', 'start_time') %}
        {% set event_message = state_attr('calendar.CALENDARNAME', 'message') %}
        {% if calendar_event %}
          {% set start_date = (calendar_event | as_datetime | as_local).date() %}
          {% set today_date = now().date() %}
          {{ 'on' if event_message in event_messages and start_date == today_date else 'off' }}
        {% else %}
          off
        {% endif %}
  - service: input_boolean.turn_{{ holiday }}
    target:
      entity_id: input_boolean.event_ROOM
mode: single

Just keep in mind that using the state object of a calendar entity will be unreliable any time the calendar has overlapping events… it may not be that big an issue since you are checking at 04:30, but if you experience issues with the blinds not opening at the right time that’s the first thing I would check.

EDIT: Corrected start_date variabe definition

Thanks for this code.
I just used it and triggered it manually.

The strange thing is that the boolean now is set to “Off” however there is a calendar event “Zomervakantie” for today.

I would expect the boolean to be “On”…

Could this be the problem:
start_date == today_date

Since The summerholiday (=Zomervakantie) started 5 weeks ago and will end next week.
I don’t have a seperate event for each day…

What were the values for

{{ state_attr('calendar.CALENDARNAME', 'start_time') }}
{{ state_attr('calendar.CALENDARNAME', 'message') }}

when you manually ran the automation?

How do you mean?

When I manually ran the automation I can see the switch is being set to “Off” state.

Where/how can I find explicit value of start time and message?

I found the issue and corrected it above… in the automation you went back to the incorrect set statement for start_date that I posted the correction for in my first post…

Observe the following:

{% if calendar_event %}
  {% set start_time = as_timestamp(calendar_event) %}
  {% set start_date = start_time | timestamp_custom('%Y-%m-%d', true) %}
  {% set today_date = now().date() %}
...

start_date is a string.
today_date is a date object.

start_date == today_date will never be true, the data types must be the same for them to pass an equivalency test. The following is one way to correct the issue:

...
  {% set start_date = (calendar_event | as_datetime | as_local).date() %}
  {% set today_date = now().date() %}
....

Adjusted, but still not working:
context:
id: 01J4SF6RHX2DZ1JJQY7C49VFYP
parent_id: 01J4SF6RHXKE38CDAPKBRV4YB3
user_id: null
holiday: ‘off’

It shows off which should be on…

Did you make sure to change the entity ID to your actual calendar?

Yes I did, I checked it twice…

I’m still strungeling with the same problem.
It seems not all events are being fetched, mainwhile also the date notation seems to be weird. I would expect to get the event of today

That is why I have said, multiple times, that using the state object of a calendar entity can be unreliable for what you are trying to do.

Usually the event that populates the state and attributes of the calendar entity will be the all-day event (if one exists), but there is no way to guarantee that will be the case if there are overlapping events. That is the whole reason for the trigger-based binary sensor I proposed in Post #7, using the calendar.get_events action allows us to retrieve all the events from the calendar and use templates to find the one we need.