Can a Template Sensor hold a static variable?

Hi All–

This one has been baffling me for a while. I have a sensor template that reports back the most recently activated motion sensor. It works fine until I’m still and all sensors are in the off state. Then the template reports back as unavailable. So I researched a bit and learned of the “default” setting. It bypasses the unavailable but rather than simply reporting back the last value, it gives the text of the sensor definition (please see below, it’s hard to explain):

  {% set a = states.sensor.last_motion %}  {## Added most recently, hoping to retrieve the last state before reevaluating  ##} 
  {% set t = expand('binary_sensor.master_motion_all') | selectattr('state','eq','on') | map(attribute='last_changed') | max | default(0) %} {##  Adding the "default(0)" prevents the "Unavailable" state ##}
  {% if( t != 0 ) -%} {##  Added to prevent reevaluation when t variable = 0 ##}
    {% set s = (expand('binary_sensor.master_motion_all') | selectattr('state','eq','on') | selectattr('last_changed', 'eq', t) | list)[0] %}
    {% if( s | selectattr('state', 'eq', 'on') ) -%}
        {% set i = s.name %}
            {% set j = i.split(' ')[0] %}
            {% set a = j %}
    {%- endif %}
  {%- else %}
      {% set a = a %}
  {%- endif %}
  {{ a }}

As mentioned above, I added the first line most recently, but instead it returns:

<template TemplateState(<state sensor.last_motion=unavailable; friendly_name=Last Motion @ 2022-07-09T20:24:23.394441-04:00>)>

and without the first line, it simply returns

“UndefinedError: ‘a’ is undefined”

Can anyone please offer a suggestion or workaround so that it retains the last value of “a” when no motion is detected?

Thank you.

If you just have the following,

{% set a = a %}

The template doesn’t care, which surprised me, as the ‘a’ on the right isn’t yet defined. However, once you change it to the following, you get the undefined error.

{% set a = a %}
{{ a }}

So the upshot is it doesn’t like {{ a }} if it doesn’t exist. Without the first line, you have two conditions where this can occur. The first is that set a = a and the second is the lack of an else on the second if. These conditions should probably be code like you’ve got in the first line, but using the recommended format (see the warning here).

  {% set t = expand('binary_sensor.master_motion_all') | selectattr('state','eq','on') | map(attribute='last_changed') | max | default(0) %} {##  Adding the "default(0)" prevents the "Unavailable" state ##}
  {% if( t != 0 ) -%} {##  Added to prevent reevaluation when t variable = 0 ##}
    {% set s = (expand('binary_sensor.master_motion_all') | selectattr('state','eq','on') | selectattr('last_changed', 'eq', t) | list)[0] %}
    {% if( s | selectattr('state', 'eq', 'on') ) -%}
        {% set i = s.name %}
            {% set j = i.split(' ')[0] %}
            {% set a = j %}
    {%- else %}
      {% set a = states('sensor.last_motion') %}
    {%- endif %}
  {%- else %}
      {% set a = states('sensor.last_motion') %}
  {%- endif %}
  {{ a }}

add what you started with before all of your modifications in the first post (the one that returned unavailable).

Actually, thinking what you’re actually trying to achieve, you can do it with a trigger-based template. Then you don’t need to worry about the previous state, because you can set it up to only update when there is motion. Without doing this, your entity will update every minute because of the use of now().

In my example below, the sensor.last_motion will only update when binary_sensor.all_motion changes from “off” to “on”. At all other times the value does not change, even after a restart.

- trigger:
    - platform: state
      entity_id:
        - binary_sensor.all_motion
      from: "off"
      to: "on"
  sensor:
    - name: Last Motion
      unique_id: last_motion
      state: >-
        {% set x = expand('binary_sensor.all_motion')
                  | selectattr('state', 'eq', 'on')
                  | list
                  | sort(attribute='last_changed') %}
        {% if (x|length != 0) %}
          {{ x[0].name }}
        {% else %}
          Should never happen
        {% endif %}
      attributes:
        when: >-
          {{ now().strftime('%d/%m/%Y %-I:%M%p') }}
        triggered: >-
          {{ now() }}

I’m sorry, this seems like it would work but there must be some aspect that I’m failing to grasp. The whole bit of code goes into config right? Not split between config and automations? Here’s how I have it now:

in one section of config:

trigger:
  - platform: state
    entity_id:
      - binary_sensor.master_motion_all
    from: "off"
    to: "on"

in another section (where my sensors are grouped):

sensor:
  - name: Last Motion
    unique_id: last_motion
    state: >-
      {% set x = expand('binary_sensor.master_motion_all')
                | selectattr('state', 'eq', 'on')
                | list
                | sort(attribute='last_changed') %}
      {% if (x|length != 0) %}
        {{ x[0].name }}
      {% else %}
        Should never happen
      {% endif %}
    attributes:
      when: >-
        {{ now().strftime('%d/%m/%Y %-I:%M%p') }}
      triggered: >-
        {{ now() }}

I feel like perhaps it’s missing a platform? But I tried to add

  - platform: template
    sensors:

and it remains unavailable.
I’ve never used a trigger-based template before so I’m not even certain how to begin to troubleshoot. The state code did work properly in dev tools so it’s I’m sure something silly but I haven’t the foggiest. Thank you man.

It’s a “new format” template sensor, which would go in “templates.yaml”, assuming you have the following in “configuration.yaml”:

template: !include templates.yaml

(Sorry, typing this on an iPad Mini for the first time, which is a terrible mistake)

EUREKA! It took a little tweaking but it worked!! Thank you SO SO much man!!