Stupid templating question I'm having a mental block with

I have the below for 5-6 situations, how can I either make this a template or consolidate this somehow for multiple devices.

- alias: 'Master Closet Motion Light - ON'
  trigger:
    - platform: state
      entity_id: binary_sensor.master_closet_motion
      to: 'on'
  action:
    - service: switch.turn_on
      entity_id: switch.master_closet_light      
      
- alias: Master Closet Motion Light - OFF
  trigger:
    - platform: state
      entity_id: binary_sensor.master_closet_motion
      to: 'off'
  action:
    - service: switch.turn_off
      entity_id: switch.master_closet_light 

## ---------------------------------------------------- 
- alias: 'Laundry Room Motion Light - ON'
  trigger:
    - platform: state
      entity_id: binary_sensor.laundry_room_motion
      to: 'on'
  action:
    - service: switch.turn_on
      entity_id: switch.laundry_room      
      
- alias: Laundry Room Motion Light - OFF
  trigger:
    - platform: state
      entity_id: binary_sensor.laundry_room_motion
      to: 'off'
  action:
    - service: switch.turn_off
      entity_id: switch.laundry_room       

If you change switch.laundry_room to switch.laundry_room_light, then you could do all that with this one automation:

- trigger:
    platform: state
    entity_id:
      - binary_sensor.master_closet_motion
      - binary_sensor.laundry_room_motion
  condition:
    condition: template
    value_template: >
      {{ trigger.from_state and trigger.to_state and
         trigger.from_state.state != trigger.to_state.state and
         trigger.to_state.state in ('on', 'off') }}
  action:
    service_template: "switch.turn_{{ trigger.to_state.state }}"
    data_template:
      entity_id: >
        {{ trigger.entity_id.replace('binary_sensor', 'switch')
                            .replace('_motion', '_light') }}

EDIT: Fixed typo per comments below.

I just found that on another thread and am working to change everything right now. Thanks!

Can you explain this??

I understand this (IE: Off Not Equals On)
trigger.from_state.state(IE:on) != trigger.to_state.state(IE:off)

as well as the last part (Make sure the TO state is either on/off)
and trigger.to_state in ('on', 'off') }}

However, I’m losing it with
trigger.from_state and trigger.to_state

Are we essentially getting the 2 variables first before processing them? I suppose all the AND’s are throwing me off.

Apologies, I’m just trying to figure out the expression for further study. :slight_smile:

For a state trigger, the trigger variable will hold the “before” (trigger.from_state) and “after” (trigger.to_state) states of the entity that changed. It is possible for one or the other to be None. E.g., when the entity’s state is first recorded in the State Machine, the before state will be None. If an entity is ever deleted, the after state will be None. The expression in question simply makes sure that both the before and after states are true state objects (so that, e.g., trigger.from_state.state will not cause an error.)

Ok, so we’re basically saying …

if not_null(from.state) and not_null(to.state)

sweet, thanks!!!

Yes. If you want to write it more explicitly, you could write it this way:

trigger.from_state is not none and trigger.to_state is not none

But since None evaluates to False, and a State Object would evaluate to True (in a Boolean expression), the way I originally wrote it works and is basically a short cut.

I cannot get this to trigger for some reason. motion sensor fires but it does not trigger the automation.

Thank you so much for this… However you had one small error which I had to sort out. I had to add the state attribute by changing

trigger.to_state in ('on', 'off')
to
trigger.to_state.state in ('on', 'off')

Here is the final automation I used which works!

  trigger:
    platform: state
    entity_id:
      - binary_sensor.master_closet_motion
      - binary_sensor.laundry_room_motion
      - binary_sensor.half_bathroom_motion
      - binary_sensor.night_stand_motion
  condition:
    condition: template
    value_template: >
      {{ trigger.from_state and trigger.to_state and
         trigger.from_state.state != trigger.to_state.state and
         trigger.to_state.state in ('on', 'off') }}
  action:
    service_template: "switch.turn_{{ trigger.to_state.state }}"
    data_template:
      entity_id: >
        {{ trigger.entity_id.replace('binary_sensor', 'switch').replace('_motion', '') }}

Is that ‘belt and suspenders’ condition really necessary?

It checks for:

  • the existence of from_state (which the template doesn’t use)
  • the existence of to_state (the automation just triggered so it must have a to_state)
  • from_state not being the same as to_state (a binary sensor has no attributes and only two states so how can it ever change from off to off or on to on?)
  • the state being either on or off (unless these devices can be unknown, the state can only be on or off)

:man_shrugging:

While testing it last night to troubleshoot why it wasn’t initially working, I took the condition totally out and it “worked”. However, I would rather leave it in there as it is not hurting anything when the desired action happens and, in the off chance something is messed up, it would catch it.

I understand your point, however, I think it’s better to have conditions that validate your input rather than just let anything through and hope it works out. :slight_smile:

I agree except three-quarters of the things it’s guarding against are unlikely to ever happen. However, if you really want to cover all the bases then you may as well also check that the triggering entities are actually binary_sensors (before you replace the string with switch). If you think that’s extreme, that’s how I feel about the need to confirm to_state exists in an automation that just got triggered. :thinking:

Oops, sorry about that. I’ll go back and edit my post so others don’t get tripped up by it in the future.

Glad it worked for you. :slight_smile:

Good questions…

Yes, it is used.

Not true. If the entity is deleted, it will cause a state_changed event (that will trigger the automation) where trigger.to_state will be None.

There’s no rule that says a binary_sensor can’t have attributes. In fact most do (even if it’s only friendly_name.) And, e.g., a Template Binary Sensor can have attributes that change (e.g., icon_template, entity_picture_template & attribute_templates.) Further, (per below) it’s possible a binary_sensor’s state might change from off to, say, unknown, in which case the service call would cause an error.

Yes, a binary_sensor’s state could be unknown or unavailable. “Binary” only refers to its normal states.

I’ll grant you for many binary_sensors many of these conditions probably won’t happen during normal use, and one could simplify quite a bit. But I preferred to provide an example I feel more comfortable with which is more robust.

1 Like

Now that seems a bit extreme. :wink: If you check the trigger section, only binary_sensor entities are monitored, so trigger.entity_id can only start with “binary_sensor” in this automation.

Thank you, Phil. An excellent description of the possible edge-cases. Of course, I now want to test these edge-cases.

  • Where and how is from_state used in the automation above? I see no reference to it other than in the condition itself.

  • How do you delete an entity to cause a state_changed event? In my admittedly limited time with Home Assistant, I only know how to delete an entity by erasing its config and restarting Home Assistant. Is there a service to delete an entity?

  • You’re right, friendly_name is an attribute even for a bog-standard binary_sensor. However, how would you dynamically change the friendly_name to cause a state change?

Yep, that’s where it’s used.

It’s not that simple. And, admittedly, this probably won’t happen for most entities. So the condition is more general than it probably needs to be in this exact scenario. But there are integrations that can cause entities to come and go. And I think (although don’t quote me on this) if an entity is changed, say via the entity registry (e.g., by clicking on its gear and changing its name or something), that actually causes the existing entity to (effectively) be deleted and re-created. I haven’t thoroughly tested all these scenarios. This is just my impression of what might happen after browsing through a lot of code.

Possibly via customization. It can also happen in a python_script. I think HA’s REST API is another possibilty. And there may be other ways, too.

Sorry, I’ve been an embedded software engineer for 40 years. If I’ve learned anything, it’s that if something can happen, it will happen. :slight_smile:

A small aside, I recently had to change ‘a LOT’ of my automation triggers from : -
to: ‘on’
to
from: ‘off’
to: ‘on’
As this caused a problem when changing brightness levels on lights, where they happen to already be on. This ‘has’ to use the light.turn_on platform which as a consequence was cancelling the timers associated with those lights, without turning the timers back on again (which is part of another ‘on’ trigger. So generally in favour of belt, braces, a bit of string and duct tape if you’ve got it. : - )

Sorry, not exactly following that.

First, if a light’s attributes change (e.g., brightness), but the light stays on, a state trigger which uses just to: 'on' should not trigger. If it is triggering, then almost certainly the state is changing to off (or something else???) and then back on (probably very briefly.) I would have to guess it changed to something else, because if it had changed to off, then your change should not have had any effect.

Second, I’m not sure what timers you’re referring to.

Firstly, I’d like to apologise to jwoodard for hi-jacking his thread.

Phil,
Yeah it threw me too. I have an automation that when it starts, triggered by the light coming on, checks that timer enable is set for that light and if it is, it cancels the old timer (script) and then re-initialises it

  - alias: au_light_wc_offdelay
    trigger:
      - platform: state
        entity_id: light.fib_fgd212dim2_wc_level
        from: 'off' ################# <--- This the bit in question here
        to: 'on'
    condition:
      - condition: state
        entity_id: input_boolean.ib_light_wc_timer_enable
        state: 'on'
    action:
      - service: script.turn_off
        entity_id: script.sc_light_wc_timer
      - service: script.sc_light_wc_timer

So the script takes a number from an input_number and waits that length of time before turning the light off. (Interesting point here is that after the light turns on the script shows running, turn the script off (unused items scripts toggle the said script) it appears to turn off then comes back on after 4 secs or so, wait the full period and it then turns the script off itself - but it was not running as the light does not turn off it just stays on. But the above automation is just to set the ‘delay off’ .
Regardless, the above script (without the " from: ‘off’ ") worked fine … (this was stage 1)

So the lights are set in different groups and the required light levels change through the day.
The light in a specific group comes on and another automation checks what the level should be and applies it : -

  - alias: au_aalight_lghtonbth
    trigger:
      - platform: state
        entity_id: light.fib_fgd212dim2_bath_level, light.fib_fgd212dim2_ensu_level, light.fib_fgd212dim2_wc_level
        from: 'off'
        to: 'on'
    action:
      - service: light.turn_on
        data_template:
          entity_id: "{{ trigger.entity_id }}"
          brightness_pct: "{{ states('input_number.in_aalight_bth_lvl') | int }}"

(This was stage 2) This sets the level to that stored in : - in_aalight_bth_lvl
It triggers an on but that’s the on that drives the timer AOK
Everything works tickety-boo

Now lets break things (with stage 3).
So I have a light on and the time of day changes, so I need to change the brightness
That is done and the number stored in (for this case) input_number.in_aalight_bth_lvl
A change in that triggers another automation : -

  - alias: au_light_adjust_brightness_wc
    trigger:
      - platform: state
        entity_id: input_number.in_aalight_bth_lvl
    condition:
      - condition: state
        entity_id: light.fib_fgd212dim2_wc_level
        state: 'on'
    action:
      - service: light.turn_on
        entity_id: light.fib_fgd212dim2_wc_level
        data_template:
          brightness_pct: >
            {{ states('input_number.in_aalight_bth_lvl') | int }}

So the number changes, triggers the script which then checks that the light is on (remember if I have to switch it on the level is taken care of). So trigger given, condition met, so action follows and to set brightness I have to turn on a light (that is already on) but pass it a new brightness. This some how breaks the timer and I’m left with a light that stays on forever (well, until the electricity company cuts my power for non-payment of bills).

I added the : -
from: ‘off’
And my timers work once again
It’s weird but this implies the trigger is not the turning ‘on’ of a state but simply trying to set it to ‘on’ regardless that it was already on

Sorry for the long story
I really wanted your (or petro’s) help on the other item : Setting Time Slot States on Re-Start (works but ...) which is about templating not working in a script.
Cheers
Mutt