home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
70.97k stars 29.62k forks source link

Strange update of template sensors when using this.sate or this.attributes.xxxxx #78358

Closed andrewjswan closed 1 year ago

andrewjswan commented 1 year ago

The problem

Icon not updating on trigger binary sensor with auto off, the state changes as expected, but the icon does not change.

What version of Home Assistant Core has the issue?

core- 2022.9.2

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant OS

Integration causing the issue

Template

Link to integration documentation on our website

https://www.home-assistant.io/integrations/template/

Diagnostics information

No response

Example YAML snippet

  - trigger:
      - platform: event
        event_type: anyone_came_in_home
    binary_sensor:
      - name:  Anyone Came In Home
        auto_off: '00:03:00'
        state: "true"
        icon: "{{ this.state | bool(false) | iif('mdi:home-plus', 'mdi:home-outline') }}"
        unique_id: anyone_came_in_home

Anything in the logs that might be useful for us?

No response

Additional information

No response

probot-home-assistant[bot] commented 1 year ago

template documentation template source (message by IssueLinks)

probot-home-assistant[bot] commented 1 year ago

Hey there @phracturedblue, @tetienne, @home-assistant/core, mind taking a look at this issue as it has been labeled with an integration (template) you are listed as a code owner for? Thanks! (message by CodeOwnersMention)

andrewjswan commented 1 year ago

@tdejneka in my case icon always mdi:home-outline'

andrewjswan commented 1 year ago

Why, trigger fired, state on, on | bool -> true, and icon should be mdi:home-plus In other sensors this template work without problem

andrewjswan commented 1 year ago

@tdejneka Look: image

andrewjswan commented 1 year ago

2022.9.4 the some ...

andrewjswan commented 1 year ago

Without auto_off it doesn't update, or rather it does, but only once ...

template:
  - trigger:
      - platform: event
        event_type: calendar_event_list_updated
    sensor:
      - name: calendar_events
        state: "{{ (state_attr('input_select.calendar_events_list', 'options') | join('|') | replace ('Нет событий','')).split('|') | select('ne', '') | unique | list | count }}"
        attributes:
          events: "{{ state_attr('input_select.calendar_events_list', 'options') | list }}"
        icon: "{{ (this.state | int(0) > 0) | iif('mdi:calendar-star', 'mdi:calendar-blank') }}"
        unique_id: calendar_events

PS: It seems that when the sensor is updated, the icon based on this.sate is updated first, and then the state of the sensor itself is updated

andrewjswan commented 1 year ago

This sensor also has a status of unavailable all the time.

template:
      - name: "Ragweed pollen"
        unit_of_measurement: "p/m³"
        icon: mdi:sprout
        unique_id: ragweed_pollen
        state: >
          {{ states('sensor.accuweather_ragweed_pollen_0d') }}
        attributes:
          level: >-
            {%- set pollen = { 'Low':'Низкий', 'Hight':'Высокий' } -%}
            {%- set state = state_attr('sensor.accuweather_ragweed_pollen_0d','level') -%}
            {{ pollen[state] if state in pollen else state }}
        availability: >
          {{ this.state | is_number }}
andrewjswan commented 1 year ago

core- 2022.9.6 The some

andrewjswan commented 1 year ago

Test sensor:

template:
  - trigger:
      - platform: time_pattern
        minutes: "/1"
    sensor:
      - name: Test Template Sensor
        state: "{{ now().minute }}"
        icon: "{{ (this.state | int(0) % 2 == 0) | iif('mdi:home-floor-0','mdi:home-floor-1') }}"
        unique_id: test_template_sensor

Test card:

type: vertical-stack
cards:
  - type: markdown
    content: >-
      {{ states("sensor.test_template_sensor") }} % 2 == 0 -> {{
      states("sensor.test_template_sensor") | int(0) % 2 == 0 }}
    title: TEST
  - type: entity
    entity: sensor.test_template_sensor
    state_color: true

Result: image image image image

andrewjswan commented 1 year ago

Same when using trigger in sensors, first the state of this.sate or this.attributes.xxxxxx is updated based on the old value, and only then the state is updated. image

template:
  - trigger:
      - platform: template
        value_template: "{{ now().minute == 0 }}"
    sensor:
      - name: Disks - Lower space
        state: "{{ this.attributes.disks | count }}"
        attributes:
          disks: >
            {%- macro disk_space() -%}
              {%- set ns = namespace(disks=[]) -%}
              {% set threshold = (states('input_number.discs_free_space_lower') | int(10)) * 1024 * 1024 * 1024 %}
              {%- for disk in states['sensor'] if ('_disk_' in disk.entity_id and state_attr(disk.entity_id, 'avail') != None and state_attr(disk.entity_id, 'avail') | int(0) < threshold ) -%}
                {%- set json = {disk.name: (state_attr(disk.entity_id, 'avail') | int(0) / 1024 / 1024 / 1024) | round(1) ~ "Gb" } -%}
                {%- set ns.disks = ns.disks + [ json ] -%}
              {% endfor %}
              {{ ns.disks }}
            {%- endmacro -%}
            {{ disk_space() }}
        icon: mdi:harddisk
andrewjswan commented 1 year ago

Hi! @PhracturedBlue, @tetienne, any ideas?

andrewjswan commented 1 year ago

Hi! @akloeckner any ides?

akloeckner commented 1 year ago

Yes: both the attributes (e.g. icon) and the state update at the same time. In this case, when anyone_came_in_home. During the update, the variable this holds data from before the update! So, this.state is off in this case.

I can think of three approaches:

I think, this is mentioned somewhere in the docs. And the issue has been discovered previously. Maybe, someone could clarify the docs further? Hacktoberfest 2022 is on! ;-)

akloeckner commented 1 year ago

See also: https://github.com/home-assistant/core/issues/60382

andrewjswan commented 1 year ago

I couldn't find it in the documentation, nor could I find Issue. But since the state is remembered before, which is very strange, it seems a bit pointless. And not logical. After all, we expect this.state to be updated when the state changes, but it doesn't.

andrewjswan commented 1 year ago

See also: #60382

Thanks, I'll have a look and try it, but so far it feels like crutches, and unnecessary action. It made more sense to do it in the sensor itself.

andrewjswan commented 1 year ago

@akloeckner But there's no trigger here, but it doesn't work either https://github.com/home-assistant/core/issues/78358#issuecomment-1257918708

akloeckner commented 1 year ago

I agree, it is not very intuitive.

But it's also not easy to address in a general way. We would need, e.g.

So, currently, the iteration must be enabled for selected self-references only. From the top of my head, I don't see a general improvement to it.

I'll check you links later!

andrewjswan commented 1 year ago

I'll check you links later!

It seems to me that here we will get a permanent status of On:

  - trigger:
      - platform: event
        event_type: anyone_came_in_home
      - platform: state
        entity_id: binary_sensor.anyone_came_in_home
    binary_sensor:
      - name:  Anyone Came In Home
        auto_off: '00:03:00'
        state: "true"
        icon: "{{ this.state | bool(false) | iif('mdi:home-plus', 'mdi:home-outline') }}"
        unique_id: anyone_came_in_home

I.e. the trigger is triggered, we update the status, after some time, the sensor is switched off i.e. the status has changed and the trigger is triggered again, and there in a loop.

andrewjswan commented 1 year ago

Checked, recursive constant updating.

andrewjswan commented 1 year ago
  • to use state(this.entity_id), but

state(this.entity_id) or states(this.entity_id)

I try states - icon in not updated ...

  - trigger:
      - platform: event
        event_type: anyone_came_in_home
    binary_sensor:
      - name:  Anyone Came In Home
        auto_off: '00:03:00'
        state: "true"
        icon: "{{ states(this.entity_id) | bool(false) | iif('mdi:home-plus', 'mdi:home-outline') }}"
        unique_id: anyone_came_in_home

And in another sensor i get error on start:

2022-10-04 17:37:47.945 ERROR (MainThread) [homeassistant.helpers.template_entity] TemplateError('UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'temperature'') while processing template ...

In attributes like:

          steam_pressure: >-
            {%- set temperature = this.attributes.temperature | float(0) -%}
            .....

When i use state_attr('sensor.xxxx', 'temperature') i dont have this error ...

andrewjswan commented 1 year ago

This is how it works, but of course it looks like crutches. :(

  - trigger:
      - platform: event
        event_type: anyone_came_in_home
        id: "true"
      - platform: state
        entity_id: binary_sensor.anyone_came_in_home
        to: 'on'
        id: "true"
      - platform: state
        entity_id: binary_sensor.anyone_came_in_home
        to: 'off'
        id: "false"
    binary_sensor:
      - name:  Anyone Came In Home
        auto_off: '00:03:00'
        state: "{{ trigger.id }}"
        icon: "{{ trigger.id | bool(false) | iif('mdi:home-plus', 'mdi:home-outline') }}"
        unique_id: anyone_came_in_home

this.state - gave beauty and brevity, but it turns out that it is almost impossible to use. Just replaced almost all the sensors that worked strangely with this.state, this.attributes with direct use of HA functions, a shame.

akloeckner commented 1 year ago

As I admitted earlier: yes, it is still somewhat unintuitive. But it's better than before. (You had to repeatedly type the sensor's name in the templates before.) And I have currently no idea how to make it perfect with a reasonable amount of effort. Some tipps:

I tried to optimize your code with these remarks in mind:

  - trigger:
      - platform: event
        event_type: anyone_came_in_home
      - platform: state
        entity_id: binary_sensor.anyone_came_in_home
        to: ~
    binary_sensor:
      - name:  Anyone Came In Home
        auto_off: '00:03:00'
        state: "{{ trigger.type == 'event' or this.state == 'on' }}"
        icon: "{{ this.state | bool(false) | iif('mdi:home-plus', 'mdi:home-outline') }}"

It's untested, but something in these lines should work.

PS: When re-reading this, I get the feeling that registering a default state trigger for each sensor using this might already be a good improvement.

andrewjswan commented 1 year ago
  • If any of the templates generate an error when rendered, the sensor will become unavailable. I believe your unavailable sensor might just be a faulty template. Try this syntax instead: variable is number.

There's a complex design there, lots of attributes used in other attributes, and they end up being used in getting the state, avaliable template checks all states, but you can see that at startup it's not as good as you wanted and at some point an error occurs. Adding checks is to complicate the code, using state_attr removes all these errors, but the code becomes less nice.

  • The initial state of a trigger-based sensor will be just unknown with no attributes defined. So, this.attributes.my_attribute will always give errors during startup. You can avoid this by using if 'my_attribute' in this.attributes or by using the function state_attr(this.entity_id, 'my_attribute'). Both these variants will gracefully handle the undefined attribute. The same holds true for states(this.entity_id).

The documentation says that all template-based sensors retain their state when the Home Assistant is restarted.

  • If you want to trigger an update on state change, but ignore attribute changes, define to: ~. This will avoid some unnecessary iterations, possibly.

  • In general, try to define your states and attributes as a fix-point iteration. This means: when you accidentally trigger an update while the sensor is already in its target state, it should not change. Home Assistant has a safeguard to stop endless re-triggering of updates, which will not be activated this way. As a rule of thumb, Home Assistant allows as many repeated updates as there are attributes defined on the sensor. You will probably end up using default() filters with this tipp.

I know that and will use it, thank you. :)

It's untested, but something in these lines should work.

I'll try to check it out. Thanks.

andrewjswan commented 1 year ago

Architecturally, I would try to solve this problem roughly as follows

  1. update all attributes that do not have this specified.
  2. Update the state if it does not contain this.
  3. update all the attributes with this.
  4. update the state if it has this.
  5. Update Icon, Availability. I don't know how feasible this is, but if you can do it this way, it takes away 80% of the problems. IMHO
andrewjswan commented 1 year ago

It's untested, but something in these lines should work.

Checked it, it's not working at all. The status and icon don't change. :(

akloeckner commented 1 year ago

Checked it, it's not working at all. The status and icon don't change. :(

Maybe try trigger.platform instead of trigger.type? (Our stick to the id approach. I wanted to save as many lines as possible. 😉 )

andrewjswan commented 1 year ago

I dont know. But this sensor will work for everyone who adds it to the configuration and sends an event from the developer tools. You can check.

akloeckner commented 1 year ago

I'm just trying to help find the most elegant solution, which is currently feasible. I have no use for that sensor (yet). But if you have a working version, let's leave it as is. 👍

andrewjswan commented 1 year ago

Ok, Yes, my version looks like Frankenstein on crutches, but it works :) I would like a more beautiful solution, but alas.