Goodnight routine - script or automation?

I am in the process of writing a “goodnight” routine. My question is: should this be a script or routine?

The pseudo-code for my routine is the following:

if alarm.zone1 open {
  send notification
  cancel routine
}

if either garage door open {
  close garage doors
  wait 20s
  if either garage door still open {
    send notification
    cancel routine
  }
}

# rest is simple, turning off lights, setting thermostat etc...

Based on the above, I have broken the 2 sub-routines down and am coding them individually as scripts. Should they both be called from an automation that is triggered?

cheers,
J.

both if statements should be separate automations. If you want them in a single automation, then you’d need to reorganize your if statements because they are currently independent outside your attempt to add a cancel routine. The ha logic would look like this:

automation 1

if alarm.zone1 open {
  send notification
}

where

if alarm.zone1 open

is your trigger.

and

  send notification

is your action.


automation 2

if alarm.zone1 is not open and either garage door open
{
  close garage doors
  wait 20s
  if either garage door open {
    send notification
  }
}

Where

if alarm.zone1 is not open

is your trigger

and

and either garage door open

is your condition

and

  close garage doors
  wait 20s
  if either garage door open {
    send notification
  }

is your action.

These run once when the trigger is met. No reason to cancel the routine as they are not continuous.

I’ve now finished my “goodnight routine” and wanted to share here in case others are looking for inspiration. My goal was to create a routine that I would invoke at night to put the house into a nighttime mode (something I used to do manually–like an animal!).

From a high level, I wanted the routine to do the following:

  1. Make sure all exterior doors/windows are closed
    1.1. If they’re not, send me a notification and HALT
  2. Make sure the garage doors are closed
    2.1 If they’re not, attempt to close them
    2.2. Wait 20s, if still not closed, send a notification and HALT
  3. Check to see all the lights (except for master bedroom/bathroom) are off
    3.1 If not, send me an actionable notification so I can turn them off with one click
  4. If thermostat is in “heat” mode, change set-point temperature
  5. Turn on master bedside lights & master bathroom light
  6. Lock door
  7. Send me a notification that the routine was successful.

Here is the actual code:

automation.yaml:

- alias: iOS app notification turn off lights
  initial_state: True
  trigger:
    platform: event
    event_type: ios.notification_action_fired
    event_data:
      actionName: GOODNIGHT_TURN_OFF_LIGHTS 
  action:
    service: homeassistant.turn_off
    entity_id: group.upstairs_lights, group.mainfloor_lights, group.basement_lights


- alias: "goodnight"
  trigger:
    platform: event
    event_type: ios.action_fired
    event_data:
      actionName: 'Goodnight'  
    
    
  action:
    - service: script.goodnight1_security_test
    - condition: template
      value_template: "{{ is_state('group.security_main_zone', 'off') }}"
    - service: script.goodnight2_garage_door_test
    - wait_template: "{{ is_state('cover.garage_door_inner', 'closed') and is_state('cover.garage_door_outer', 'closed') }}"
      timeout: '00:00:20'
      continue_on_timeout: 'true'
    - service_template: >
       {% if is_state('cover.garage_door_inner', 'open') or is_state('cover.garage_door_outer', 'open') %}
         script.goodnight3_garage_door_notify
       {% else %}
         script.dummy
       {% endif %}
    - condition: template
      value_template: "{{ is_state('cover.garage_door_inner', 'closed') and is_state('cover.garage_door_outer', 'closed') }}"
    - service: script.goodnight4_lights_test
    - service: script.goodnight5_thermostat_test
    - service: script.goodnight6_lights_and_lock
    - service: notify.mobile_app_MY_iphone
      data:
        title: "Goodnight security"
        message: "All is well... sleep tight!"
    - event: homeassistantCube
      event_data:
        notifications: [{title: 'Goodnight security', severity: 'warning', options: {detail: 'All is well... sleep tight', dismissable: 'true'}}]

script.yaml:

dummy:
  sequence: []


goodnight1_security_test:
  sequence:
    - service_template: >
       {% if is_state('group.security_main_zone', 'on') %}
         script.goodnight1_security_zone_test
       {% else %}
         script.dummy
       {% endif %}

goodnight1_security_zone_test:
  sequence:
    - service: notify.mobile_app_MY_iphone
      data_template:
        title: "Goodnight security"
        message: >-
          The following door/window(s) are open: 
          {%- set ns = namespace(zone_list = ' ') -%}
            {%- for entity_id in expand('group.security_main_zone') -%}
              {%- if entity_id.state == 'on' -%}
                {%- set ns.zone_list = ns.zone_list + entity_id.name + ', ' -%}
              {%- endif -%}
            {%- endfor -%}    
          {{ ns.zone_list[:-2] }}
                    
goodnight2_garage_door_test:
  sequence:
    - service_template: >
       {% if is_state('cover.garage_door_inner', 'open') or is_state('cover.garage_door_outer', 'open') %}
         script.goodnight2_garage_door_close
       {% else %}
         script.dummy
       {% endif %}
          
goodnight2_garage_door_close:
  sequence:
    - service: cover.close_cover
      data_template:
        entity_id: >
          cover.dummy
          {% if is_state('cover.garage_door_inner', 'open') %}
            , cover.garage_door_inner        
          {% endif %}
          {% if is_state('cover.garage_door_inner', 'open') %}
            , cover.garage_door_outer
          {% endif %}
  
goodnight3_garage_door_notify:
  sequence:
    - service: notify.mobile_app_MY_iphone
      data:
        title: "Goodnight security"
        message: "One of the garage doors would not close."
        
goodnight4_lights_test:
  sequence:
    - service_template: >
       {% if is_state('group.upstairs_lights', 'on') or is_state('group.mainfloor_lights', 'on') or is_state('group.basement_lights', 'on') %}
         script.goodnight4_lights_notify
       {% else %}
         script.dummy
       {% endif %}    
       
goodnight4_lights_notify:
  sequence:
    - service: notify.mobile_app_MY_iphone
      data:
        message: 'Goodnight lights'
        data:
          push:
            category: "goodnight_turn_off_lights"
            
goodnight5_thermostat_test:
  sequence:
    - service_template: >
       {% if is_state('climate.kitchen', 'heat') %}
         script.goodnight5_thermostat_set
       {% else %}
         script.dummy
       {% endif %}
       
goodnight5_thermostat_set:
  sequence:
    - service: climate.set_temperature
      data:
        entity_id: climate.kitchen
        temperature: 17
        
goodnight6_lights_and_lock:
  sequence:
    - service: homeassistant.turn_on
      entity_id: switch.master_bedroom_bedside_light, light.master_bedroom_8_keypad_light, switch.master_bathroom_vanity_light
    - service: homeassistant.turn_off
      entity_id: group.outside_front_lights
    - service: lock.lock
      entity_id: lock.vision_yale_push_button_deadbolt_yrd210_locked
    - service: alarm_control_panel.alarm_arm_home
      entity_id: alarm_control_panel.main_alarm_partition
1 Like

what are you doing here? seems the if is identical, and yet you 've written 2 different entity_id’s for the template.
Never seen a template like this, does this actually work?

It works. What it is doing is creating a comma separated list of the entity_ids of the covers I want to close. The first one is cover.dummy which ensures the entity_id is not null. Without this, it falls apart.

don’t you want the second one to check if ‘cover.garage_door_outer’ is open? Else the set of templates is rather useless?

if you want a list of all covers that are open, you can use this:

{% set covers_open = states.cover | 
             selectattr('state','eq','open') | map(attribute='entity_id') | list %}

test if this list is not empty (if there are any open covers)

{% if covers_open | length != 0 %}

but, you can’t actually use the service_template on that… the outcome of the template is in fact a string, and not a comma separated list…

you need to do this:

          {% set lights_on = states.light |
                       selectattr('entity_id','in',state_attr('group.all_inside_lights','entity_id'))| 
                       selectattr('state','eq','on') | map(attribute='entity_id') | list %}

          {% set ns = namespace(lights = '') %}
          {% for i in range(lights_on | length) %}
            {% if states(lights_on[i]) == 'on' %}
            {% set d = ', ' if ns.lights | length > 0 else '' %}
            {% set ns.lights = ns.lights ~ d ~ lights_on[i] %}
            {% endif %}
          {% endfor %}
          {{ ns.lights }}

and then for covers of course… :wink:

don’t have experience with covers myself, but if you simply want to close them all at night, why don’t you put them in a group, and close the group, without further condition or template… works for lights.

compare these:

  turn_off_lights_on:
    alias: Turn off lights on
    sequence:
      service: light.turn_off
      data_template:
        entity_id: >
          {% set lights_on = states.light |
                       selectattr('entity_id','in',state_attr('group.all_inside_lights','entity_id'))| 
                       selectattr('state','eq','on') | map(attribute='entity_id') | list %}

          {% set ns = namespace(lights = '') %}
          {% for i in range(lights_on | length) %}
            {% if states(lights_on[i]) == 'on' %}
            {% set d = ', ' if ns.lights | length > 0 else '' %}
            {% set ns.lights = ns.lights ~ d ~ lights_on[i] %}
            {% endif %}
          {% endfor %}
          {{ ns.lights }}

  switch_off_inside_lights:
    alias: 'Switch off inside lights'
    sequence:
      service: homeassistant.turn_off
      entity_id: group.all_inside_lights

If you take a look, there are 2 seperate if/endif statements. I have to admit I don’t know why, but it does work. My assumption is that it did produce a comma-seperated list along the lines of:

entity_id: cover.dummy, cover.garage_door_inner, cover.garage_door_outer

As I said, this does work. I have been using it in another automation for > 1yr.

Sorry, but I see 2 identical if s there checking

is_state(‘cover.garage_door_inner’,’open’)

Here are the two separate if statements:

    - service: cover.close_cover
      data_template:
        entity_id: >
          cover.dummy
          {% if is_state('cover.garage_door_inner', 'open') %}
            , cover.garage_door_inner        
          {% endif %}
          {% if is_state('cover.garage_door_inner', 'open') %}
            , cover.garage_door_outer
          {% endif %}

Exactly, they both test for the identical if-clause. Considering you apparently want to close both covers if door_inner is open, why not try this:

- service_template: >
     {% if is_state('cover.garage_door_inner', 'open') %} cover.close_cover
     {% else %} script.dummy
     {% endif %}
  entity_id:
    - cover.garage_door_inner
    - cover.garage_door_outer

script.dummy would be no more than:

script:
  dummy:
    sequence: []

or, even cleaner:

- service_template: >
    script.{{'close_cover' if is_state('cover.garage_door_inner', 'open') else 'dummy'}}

with script.close_cover:

close_cover:
  sequence:
    service: cover.close_cover
    entity_id:
      - cover.garage_door_inner
      - cover.garage_door_outer

yes, it is a bit of an augmented hack :wink: it needs to evaluate the if and the else, and, according to the if state, select some entity_id’s.

Your current script first sets the cover.dummy (which can be seen as the ‘else’ clause if the if doesnt evaluate to true). Since the script holds only one service that needs different parameters depending on the state, is then follows with the 2 templates, that check for the identical if state, and add the 2 entity_id’s and the separating comma.

Though its still puzzles me why it would be written like that, it apparently is possible :wink:

taking it a bit more apart as I suggested above makes it much clearer what is actually happening and why. and, maybe even more importantly, makes it easily expandable, and maintainable.

1 Like

DOH… my apologies. Thank you for pursuing this… that was a typo; the second should have read cover.garage_door_outer. I’ll take a closer look at your suggestions; always up for a more readable/less “hacky” solution!

Thank you again!!!