Making attributes from state variables

My goal is to have sth like this:

template:
  - sensor:
      name: "Some name"
      state: >
        {% set full_date = ... %}
        {% set timestamp = as_timestamp(strptime(full_date, '%Y-%m-%d'), default = 2147483647) %}
        {{ timestamp | timestamp_custom('%-d %B', True, default = 'unknown') }}"
      attributes:
        full_date: "{{ full_date }}"
        timestamp: "{{ timestamp }}"

that is have my partial function returns as attributes as I’ll need them in different entities.
At first, I tried doing those in attributes and having the state template rely on attributes but HA got mad at me. I also tried "{{ this.full_date }}" and "{{ this.state.full_date }}" with no luck.

I don’t want to have multiple calls to a fcn that I already called before, because working on variables is generally better option than calling function’s.

This is my first rodeo with variables but I think something like this should be doable.

I don’t get what you want to achieve, sorry, but what I can tell is this: variables are only available in a local scope. Will say in your case, you can’t “pass” the variables from “state” to “attributes”, they simply aren’t available. :slight_smile:

If you want that, you’d have to write the template under “full_date”.

      attributes:
        full_date: >
          {% set full_date = states('...') %}
          {{ full_date }}
        timestamp: "{{ timestamp }}"

What Petro says :rofl:

this has an order of operations. You’d have to put the full date in the attribute and use it in the state, and you’d need something that updates your template.

This should update once a minute because you’re using now() and you only have to configure the date once. On startup, it will be incorrect because the state is resolved before the attributes. After that, the date will set in and you’ll have the correct birthday sensor.

template:
  - sensor:
      name: "Some name"
      state: >
        {% set full_date = this.attributes.full_date if this is defined else now().date() | string %}
        {% set timestamp = as_timestamp(strptime(full_date, '%Y-%m-%d')) %}
        {{ timestamp | timestamp_custom('%-d %B', True, default = 'unknown') }}"
      attributes:
        full_date: '1999-09-03'
        timestamp: "{{ this.attributes.full_date if this is defined else now().timestamp() }}"

This will trigger things if you make notification sensors off it for ‘today’. If you want to avoid that…

template:
  - sensor:
      name: "Some name"
      state: >
        {% set fmat = '%Y-%m-%d' %}
        {% set full_date = this.attributes.full_date if this is defined else 0 | timestamp_custom(fmat) %}
        {% set timestamp = as_timestamp(strptime(full_date, fmat)) %}
        {{ timestamp | timestamp_custom('%-d %B', True) }}"
      attributes:
        full_date: '1999-09-03'
        timestamp: "{{ this.attributes.full_date if this is defined else 0 }}"

Lastly, you only need defaults if it’s going to fail to convert. If you check properly before conversion, you don’t need defaults.

Thanks on the tip for is defined, that was the missing key to my previous attempts.

As to the updating, full_date is taken from the state of a rest sensor, so it should render the template as soon as the state of that sensor changes (i think). But that shouldn’t change much, right?

So this is what I’ve come up with using petro’s reply

  - sensor:
    - name: "Some Name"
      state: >
        {% set timestamp = this.attributes.timestamp if this is defined else 2147483647 %}
        {{ timestamp | timestamp_custom('%-d %B', True) }}
      attributes:
        full_date: "{{ state_attr('sensor.some_sensor','some_attribute')[2]['date'] if states.sensor.some_sensor.attributes.some_attribute is defined else '2038-01-19' }}"
        timestamp: "{{ as_timestamp(strptime(this.attributes.full_date, '%Y-%m-%d', default = strptime('2038-01-19', '%Y-%m-%d'))) if this is defined else 2147483647 }}"

However, it still fails

Error while processing template: Template("{% set timestamp = this.attributes.timestamp if this is defined else 2147483647 %} {{ timestamp | timestamp_custom('%-d %B', True) }}")
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 409, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1842, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1483, in timestamp_custom
    date = dt_util.utc_from_timestamp(value)
  File "/usr/src/homeassistant/homeassistant/util/dt.py", line 145, in utc_from_timestamp
    return dt.datetime.utcfromtimestamp(timestamp).replace(tzinfo=UTC)
jinja2.exceptions.UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'timestamp'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 525, in async_render_to_info
    render_info._result = self.async_render(variables, strict=strict, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 411, in async_render
    raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'timestamp'

and

TemplateError('UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'timestamp'') while processing template 'Template("{% set timestamp = this.attributes.timestamp if this is defined else 2147483647 %} {{ timestamp | timestamp_custom('%-d %B', True) }}")' for attribute '_attr_native_value' in entity 'sensor.some_sensor'

I’m not sure why you didn’t use what I wrote, because of that you introduced a series of issues.

I wrote the original one in a specific way to counter the issues that may arise.

Edit: at most, to correct it you may have to add and this to your in line if statements

Just to step back for a moment, is your sensor showing the same value literally all the time? Like is full date actually a hard-coded date and your sensor is just showing its value formatted? If so then yea I guess petro’s solution works but I don’t think this is a general solution if that’s what you’re hoping for.

Normally the state of entities keep changing. And when that happens you’ll be back to this fundamental issue:

(emphasis mine)

Which means you kind of can’t do what you’re trying to do. Variables in templates are only available within that template. And you can’t stuff values into attributes and expect them to be available in other templates because they resolve out of order.

If you want a general solution, it’s to use a trigger template sensor instead of a template one. This way you can control the order of operations by doing something like this:

template:
  - trigger:
      - id: real
        platform: homeassistant
        event: start
      - id: full_date_update
        platform: state
        entity_id: sensor.some_name
        attribute: full_date
        to:
      - id: timestamp_update
        platform: state
        entity_id: sensor.some_name
        attribute: timestamp
        to:
  - sensor:
      - name: Some name
        state: >-
          {{ states('sensor.some_name') if trigger.id != 'timestamp_update'
             else state_attr('sensor.some_name', 'timestamp')
              | timestamp_custom('%-d %B', True, default = 'unknown') }}
        attributes:
          full_date: >-
            {{ state_attr('sensor.some_name', 'full_date') if trigger.id != 'real'
                else ... }}
          timestamp: >-
            {{ state_attr('sensor.some_name', 'timestamp') if trigger.id != 'full_date_update'
                else as_timestamp(strptime(state_attr('sensor.some_name', 'full_date'), '%Y-%m-%d'), default = 2147483647) }}

With a trigger template entity you can forcibly sequence the order of operations like this. You basically have a trigger per each attribute/variable that is only used to update one part of the entity. That update triggers the next one until the entire entity is up to date in the order you want it to go.

If my understanding of your use case is correct then this is overkill and you should do what petro showed. But if you’re looking for a general solution to this problem where the real trigger can be anything (and not just used to set a constant at startup) then I think this is it.

But tbh you really have to consider whether this is worth it. With all the extra triggers the code is not only longer and more complex its almost certainly less performant then just copying and pasting the variables code.

EDIT: Btw if full_date is in fact a constant then another option is to use customization to set its value. That way it will be set when the entity is loaded into the state machine before any of these templates run. That won’t help you with timestamp since that is actually a template but at least you can always bank on full_date existing that way.

I tried to mimic it as closely as i could.

couple key changes i made:

  • attribute full_date is no longer static
  • default date should be highest date possible (end of unix time; 1st of Jan 2038)
  • attribute timestamp should actually be a unix timestamp, not just date copied over

Then this won’t work. See my post above. Attributes are rendered after state. So the best case scenario is that when the state template is rendered it will be using the previous value of full_date and timestamp since their new value will be calculated after state.

In that case you have to use my trigger template suggestion above to control the order of operations.

I have a rest sensor that has an json attribute where the date is located (in format “%Y-%m-%d” or for example “2022-06-02”)

I see. I was trying to save some function calls, but now i see, it’s not possible, thanks anyways.

Is it documented or easily verifiable somewhere in the code that I could check myself ?

I am asking this because I have the following that seem to be working:

template:
  - sensor:
      - name: Occupied rooms
        unique_id: occupied_rooms
        attributes:
          friendly_name: Cômodos ocupados
          rooms: >-
            {%- set rooms = {
              'binary_sensor.someone_living_room': "sala",
              'binary_sensor.someone_bedroom': "quarto",
              'binary_sensor.someone_kitchen': "cozinha",
              'binary_sensor.someone_bathroom': "banheiro",
              'binary_sensor.someone_social_bathroom': "banheiro social",
              'binary_sensor.someone_suite': "suíte",
            } -%}
            {%- set filtered_entities = expand(rooms.keys()) | selectattr('state', 'eq', 'on') | map(attribute = "entity_id") | list -%}
            {{- rooms.items() | selectattr('0', 'in', filtered_entities) | map(attribute = '1') | list -}}
        state: >-
          {{- this.attributes.rooms | count -}}

I just don’t know whether it works because I declared attributes before state or what.
I wanted to make sure that I shouldn’t be doing that.

PS.: except friendly_name that doesn’t work.

That will have errors in the logs because state is resolved before attributes. I don’t need to look it up, that’s how it works.

I just found this in the docs… so it seems safe to use this.attributes.something in the state
Probably, I will need to change to {{- this.attributes.rooms | default | count -}}

template:
  - sensor:
      - name: test
        state: "{{ this.attributes.test | default('Value when missing') }}"
        # not: "{{ state_attr('sensor.test', 'test') }}"
        attributes:
          test: "{{ now() }}"

Source : Template - Home Assistant

Thanks!