Where can you put variables in your automation and state based trigger templates and template sensors

Can anybody tell where you can put a variables definition in:

  • automations
  • template sensors
  • template triggers

Reason for asking:
I’ve been searching for an answer in the documentation and could not find it.
Same goes as to where you can import jinja macro’s and where you can’t.
I would like to know because I now seem to have gotten stuck in:

  • not being able to import a jinja macro in a state based trigger action variable (is that correct?)
  • not being able to define variables in a template sensor (while I can import the macro there, but I need the result at several places maybe in my state based trigger action as well)

Some background on what I’m trying to achieve.
I got a magic cube from Aqara.
A really nice remote with one flaw:
The rotate events lack information which face of the cube was activated.
Worse case: if the cube was asleep and you flip it, this flip event might get lost and your cube rotation might be interpretated under the assumption that the previous face (kept in my sensor) was still facing up.

Hence, I’ve written a macro to determine from the events that do contain the activated face, which face is currently up:

{% macro cube_face(cube_event, faces) %}
{% set is_cube_event = cube_event is defined and cube_event.event_type == 'zha_event' and cube_event.data.command in ['slide','knock','flip'] %}
{% if is_cube_event and cube_event.data.command == 'flip' %}
  {% set face_number = cube_event.data.args.activated_face | int | default(0) %}
  {% if face_number > 0 and face_number <= faces | length %}
    {{ faces[face_number-1] }}
  {% else %}
    {{ 'no face' }}
  {% endif %}
{% elif is_cube_event and cube_event.data.command in ['slide','knock'] %}
  {% set face_number = cube_event.data.args.activated_face | int | default(0) %}
  {% if face_number == 0 or face_number > faces | length %}
    {{ 'no face' }}
  {% elif face_number in [1,4] %}
    {{ faces[face_number-1] }}
  {% elif face_number in [2,3] %}
    {{ faces[face_number+2] }}
  {% else %}
    {{ faces[face_number-4] }}
  {% endif %}
{% else %}
  {{ '' }}
{% endif %}
{% endmacro %}

In my sensor I use this macro to update the activated face and would like to know which face that is to set my attributes (still a work in progress):

- trigger: 
  - platform: event
    event_type: zha_event
  action:
    - variables:
        woonkamer_cube_event: >
          {{ trigger.event is defined and 
             trigger.event.event_type == 'zha_event' and 
             trigger.event.data.device_id == '5517cc098da2aa72bd77fe7e06a3776f' and
             trigger.event.data.command in ['slide','rotate_left','rotate_right','knock','flip','shake'] }}
        woonkamer_cube_reset: >
          {{ (woonkamer_cube_event and 
             state_attr('sensor.woonkamer_magic_cube','command') == 'shake' and 
             trigger.event.data.command == 'shake') }}
        woonkamer_lights: >
          {{ ['light.dimmer_2_5','light.dimmer_2_3','light.dimmer_2_4'] }}
        woonkamer_default_light: 1
  sensor:
    - name: "Woonkamer magic cube"
      unique_id: woonkamer_magic_cube
      state: >
        {% from 'magic-cube.jinja' import cube_face %}
        {% set active_face = cube_face(trigger.event,['lights','audio','speakers','climate','power','curtains']) %}
        {% if woonkamer_cube_event and active_face != '' %}
        {{ active_face }}
        {% else %}
        {{ states('sensor.woonkamer_magic_cube') }}
        {% endif %}
      attributes:
        command: >
          {% if woonkamer_cube_event %}
            {{ trigger.event.data.command }}
          {% else %}
            {{ this.attributes.command }}
          {% endif %}
        relative_degrees: >
          {% if woonkamer_cube_event and 'rotate' in trigger.event.data.command %}
            {{ trigger.event.data.args.relative_degrees }}
          {% elif woonkamer_cube_event %}
            {{ 0.0 }}
          {% else %}
            {{ this.attributes.relative_degrees }}
          {% endif %}
        light: >
          {% if woonkamer_cube_reset or (this.state == 'lights' and trigger.event.data.command == 'shake') %}
            {{ '' }}
          {% elif woonkamer_cube_event and this.state == 'lights' and (trigger.event.data.command == 'slide' or (this.attributes.light == '' and 'rotate' in trigger.event.data.command)) %}
            {% set index = woonkamer_lights.index(this.attributes.light) + 1 if this.attributes.light != none and this.attributes.light != '' else woonkamer_default_light %}
            {% if index < woonkamer_lights | length %}
              {{ woonkamer_lights[index] }} 
            {% else %}
              {{ woonkamer_lights[0] }}
            {% endif %}
          {% else %}
            {{ this.attributes.light }}
          {% endif %}
        audio: >
          {% if woonkamer_cube_event and this.state == 'audio' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (this.state == 'audio' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.audio }}
          {% endif %}
        speakers: >
          {% if woonkamer_cube_event and this.state == 'speakers' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (this.state == 'speakers' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.speakers }}
          {% endif %}
        climate: >
          {% if woonkamer_cube_event and this.state == 'climate' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (this.state == 'climate' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.climate }}
          {% endif %}
        curtains: >
          {% if woonkamer_cube_event and this.state == 'curtains' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (this.state == 'curtains' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.curtains }}
          {% endif %}
        power: >
          {% if woonkamer_cube_event and this.state == 'power' and 'rotate' in trigger.event.data.command %}
            {% if this.attributes.power == 'bioscoop' %}
              {{ '' }}
            {% else %}
              {{ 'bioscoop' }}
            {% endif %}
          {% elif woonkamer_cube_reset or (this.state == 'power' and trigger.event.data.command == 'shake') %}
            {{ '' }}
          {% else %}
            {{ this.attributes.power }}
          {% endif %}

What I tried and failed to do is a cube_face variable to hold what I currently do to set state.
And use cube_face instead of this.state (which is old) to update my attributes.
This way, if the cube awakes I can for instance ignore all rotation events until one of the other events has occured to first determine for sure which face is up.

Automations:

YAML variables can be defined multiple places, their scope depends on where they are defined.

trigger_variables:
  var_1: true #script-wide (only Limited Templates and static values allowed)
variables:
  var_2: "{{ true }}" #script-wide
trigger:
  - platform: state
    entity_id: sensor.x
    to: y
    variables:
      var_3: "{{ false }}" #script-wide but, if you have multiple triggers, this value applies only when this specific trigger initiates the automation
condition:
  - variables:
      var_4: "{{ true }}" #local to condition block
action:
  - variables:
      var_5: "{{ true }}" #local to action block
  - choose:
      - conditions: 
          - condition: template
            value_template: "{{ var_2 }}" 
        sequence:
          - variables:
              var_6: "{{ false }}" #local to this Option of this Choose action    

State-Based Template Sensors:

YAML variables aren’t really supported in state-based template sensors. You can use the sensor’s attributes to render and store values that are used in the state template. In most cases it would be better to just set up another template sensor instead of self-referencing attributes.

Trigger-Based Template Sensors:

YAML variables can be defined at the trigger or in the action block of a trigger-based template sensor. These variables are available for use in the state template as well as any attribute templates.

trigger:
  - platform: numeric_state
    entity_id: sun.sun
    attribute: elevation
    above: -4.0
    variables:
      var_1: "{{ now().day }}"
  - platform: numeric_state
    entity_id: sun.sun
    attribute: elevation
    below: -4.0
    variables:
      var_1: 1
action:
  - variables:
      var_2: "{{ var_1 is even }}"
binary_sensor:
  - name: Example
    state: "{{ var_2 }}"
    attributes:
      day: |
        {% set prev = var_1 if this is undefined else this.attributes.day %} 
        {{  iif(var_2, var_1, prev) }}

The first thing to do is define your trigger more specifically. That trigger will fire for every ZHA event posted to the Event Bus.

Also, be aware that this.state is going to be the state of the sensor at the moment the trigger fires, not the state based on the rendering of the state template.

Many thnx, this was just that bit of information I needed!
I’m going to bookmark this, as it is the only place that now shows where you can use variables (and their scope!).
This is were I’m now and works like a charm, now I can start working on my attributes :sweat_smile:

- trigger: 
  - platform: event
    event_type: zha_event
    event_data:
      device_id: 5517cc098da2aa72bd77fe7e06a3776f
    variables:
      active_face: >
        {% from 'magic-cube.jinja' import cube_face %}
        {% set face = cube_face(trigger.event,['lights','audio','speakers','climate','power','curtains']).strip() %}
        {{ face if face != '' else states('sensor.woonkamer_magic_cube') }}
      woonkamer_cube_event: >
        {{ trigger.event is defined and 
           trigger.event.data.command in ['slide','rotate_left','rotate_right','knock','flip','shake'] }}
      woonkamer_cube_reset: >
        {{ (woonkamer_cube_event and 
           state_attr('sensor.woonkamer_magic_cube','command') == 'shake' and 
           trigger.event.data.command == 'shake') }}
      woonkamer_lights: >
        {{ ['light.dimmer_2_5','light.dimmer_2_3','light.dimmer_2_4'] }}
      woonkamer_default_light: 1
  sensor:
    - name: "Woonkamer magic cube"
      unique_id: woonkamer_magic_cube
      icon: mdi:cube-scan
      state: >
        {{ active_face }}
      attributes:
        command: >
          {% if woonkamer_cube_event %}
            {{ trigger.event.data.command }}
          {% else %}
            {{ this.attributes.command }}
          {% endif %}
        relative_degrees: >
          {% if woonkamer_cube_event and 'rotate' in trigger.event.data.command %}
            {{ trigger.event.data.args.relative_degrees }}
          {% elif woonkamer_cube_event %}
            {{ 0.0 }}
          {% else %}
            {{ this.attributes.relative_degrees }}
          {% endif %}
        light: >
          {% if woonkamer_cube_reset or (active_face == 'lights' and trigger.event.data.command == 'shake') %}
            {{ '' }}
          {% elif woonkamer_cube_event and active_face == 'lights' and (trigger.event.data.command == 'slide' or (this.attributes.light == '' and 'rotate' in trigger.event.data.command)) %}
            {% set index = woonkamer_lights.index(this.attributes.light) + 1 if this.attributes.light != none and this.attributes.light != '' else woonkamer_default_light %}
            {% if index < woonkamer_lights | length %}
              {{ woonkamer_lights[index] }} 
            {% else %}
              {{ woonkamer_lights[0] }}
            {% endif %}
          {% else %}
            {{ this.attributes.light }}
          {% endif %}
        audio: >
          {% if woonkamer_cube_event and active_face == 'audio' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (active_face == 'audio' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.audio }}
          {% endif %}
        speakers: >
          {% if woonkamer_cube_event and active_face == 'speakers' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (active_face == 'speakers' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.speakers }}
          {% endif %}
        climate: >
          {% if woonkamer_cube_event and active_face == 'climate' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (active_face == 'climate' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.climate }}
          {% endif %}
        curtains: >
          {% if woonkamer_cube_event and active_face == 'curtains' and trigger.event.data.command not in ['shake','flip'] %}
            {{ 'manual' }}
          {% elif woonkamer_cube_reset or (active_face == 'curtains' and trigger.event.data.command == 'shake') %}
            {{ 'auto' }}
          {% else %}
            {{ this.attributes.curtains }}
          {% endif %}
        power: >
          {% if woonkamer_cube_event and active_face == 'power' and 'rotate' in trigger.event.data.command %}
            {% if this.attributes.power == 'bioscoop' %}
              {{ '' }}
            {% else %}
              {{ 'bioscoop' }}
            {% endif %}
          {% elif woonkamer_cube_reset or (active_face == 'power' and trigger.event.data.command == 'shake') %}
            {{ '' }}
          {% else %}
            {{ this.attributes.power }}
          {% endif %}

And my macro which I had to update after upgrading (apparently a fix in HA or Aqara):

  • Core 2024.4.3
  • Supervisor 2024.04.0
  • Operating System 12.2
  • Frontend 20240404.2
{% macro cube_face(cube_event, faces) %}
{% set is_cube_event = cube_event is defined and cube_event.event_type == 'zha_event' and cube_event.data.command in ['slide','knock','flip'] %}
{% if is_cube_event %}
  {% set face_number = cube_event.data.args.activated_face | int | default(0) %}
  {% if face_number > 0 and face_number <= faces | length %}
    {{ faces[face_number-1] }}
  {% else %}
    {{ 'no face' }}
  {% endif %}
{% else %}
  {{ '' }}
{% endif %}
{% endmacro %}