I have this template that checks every hour if, in the next 24 hours, the temperature will be higher or lower than the values I've set through some input_text helpers. I'm using the state of the sensor to display / hide a visual alert on my dashboard and the result_temperatures and result_datetime attributes to indicate the temperature that triggered the alert information.
- sensor:
- name: Temperature alert trigger
# compare all sensor.forecast_24_hours temperatures to both temp_alert values
unique_id: sensor.temp_alert_trigger
attributes:
result: >-
{% set temperatures = state_attr('sensor.forecast_24_hours', 'forecast') %}
{% set alert_value_max = states('input_text.temp_alert_value_max') %}
{% set alert_value_min = states('input_text.temp_alert_value_min') %}
{% set ns = namespace(temp_list = []) %}
{% for temp in temperatures %}
{% if temp.temperature | int > alert_value_max | int %}
{# {{temp.temperature}} > {{alert_value}} #}
{% set ns.temp_list = ns.temp_list + [(temp.temperature,temp.datetime)] %}
{% set ns.type = "max" %}
{% endif %}
{% if temp.temperature | int < alert_value_min | int %}
{# {{temp.temperature}} < {{alert_value}} #}
{% set ns.temp_list = ns.temp_list + [(temp.temperature,temp.datetime)] %}
{% set ns.type = "min" %}
{% endif %}
{% endfor %}
{% if ns.type == "max" %}
{{ ns.temp_list | max }}
{% else %}
{{ ns.temp_list | min }}
{% endif %}
result_temperatures: '{{ "0" if this.attributes.result is not defined else this.attributes.result[0] }}'
result_datetime: "{{ '0' if this.attributes.result is not defined else as_timestamp(this.attributes.result[1], 0) | timestamp_custom('%B %d at %H:%M', 0) }}"
state: '{{ "off" if this.attributes.result == "" else "on" }}'
It works as I need it (though it's my first template, and I'm pretty sure it's not optimized, or shall I say messy; any recommendations are more than welcome :)).
But when there are no values in the list (meaning no temperature higher or lower than expected in the next 24 hours) I get a warning ""No aggregated item, sequence was empty"
Template variable warning: No aggregated item, sequence was empty. when rendering '{% set temperatures = state_attr('sensor.forecast_24_hours', 'forecast') %} [...]'
Template variable warning: str object has no element 0 when rendering '{{ "0" if this.attributes.result is not defined else this.attributes.result[0] }}'
Template variable warning: str object has no element 1 when rendering '{{ '0' if this.attributes.result is not defined else as_timestamp(this.attributes.result[1], 0) | timestamp_custom('%B %d at %H:%M', 0) }}'
First, I don't understand what I'm doing wrong in the conditions result_temperatures: '{{ "0" if this.attributes.result is not defined else this.attributes.result[0] }}' (and result_datetime:). How can I check if this.attribut.result has values before trying to access them? I thought I was doing just that, but obviously not.
Also how can I prevent the warning from being fired if result is empty (which is a normal behavior most of the time) ? I've tried to understand how I could use the availability: template, but I don't get how to use it in my code, and I'm not sure if it's the way to go, because from what I understood, the template won't be evaluated if the list turns out to be empty, but I need the state to be updated if the list becomes empty.
Thank you for any help that might point me in the right direction.
What I think happens is this (take it with a grain of salt ):
the warning comes from {{ ns.temp_list | max }} and empty list; it doesn't seem to be allowed, and the output is undefined
undefined returned from a variable or attribute renders as empty string
that's why "0" if this.attributes.result is not defined is almost*) never true, and it tries go get a first/second character from empty string
*) you still need to guard against undefined attribute value though, because template sensors become unavailable with attributes cleared when the platform reloads or HA starts.
You could try this:
- sensor:
- name: Temperature alert trigger
# compare all sensor.forecast_24_hours temperatures to both temp_alert values
unique_id: sensor.temp_alert_trigger
attributes:
result: >-
{% set temperatures = state_attr('sensor.forecast_24_hours', 'forecast') or {} %}
{% set alert_value_max = states('input_text.temp_alert_value_max') %}
{% set alert_value_min = states('input_text.temp_alert_value_min') %}
{% set ns = namespace(temp_list = []) %}
{% for temp in temperatures %}
{% if temp.temperature | int > alert_value_max | int %}
{# {{temp.temperature}} > {{alert_value}} #}
{% set ns.temp_list = ns.temp_list + [(temp.temperature,temp.datetime)] %}
{% set ns.type = "max" %}
{% endif %}
{% if temp.temperature | int < alert_value_min | int %}
{# {{temp.temperature}} < {{alert_value}} #}
{% set ns.temp_list = ns.temp_list + [(temp.temperature,temp.datetime)] %}
{% set ns.type = "min" %}
{% endif %}
{% endfor %}
{% if ns.temp_list|count > 0 %}
{% if ns.type == "max" %}
{{ ns.temp_list | max }}
{% else %}
{{ ns.temp_list | min }}
{% endif %}
{% else %}
None
{% endif %}
result_temperatures: '{{ "0" if this.attributes.result|default(none) is none else this.attributes.result[0] }}'
result_datetime: "{{ '0' if this.attributes.result|default(none) is none else as_timestamp(this.attributes.result[1], 0) | timestamp_custom('%B %d at %H:%M', 0) }}"
state: '{{ "off" if this.attributes.result|default(none) is none else "on" }}'
Ah, and with the state switching on/off, you could make it a binary_sensor.
I would probably structure this a bit differently.
If the state in going to to "on/off", use a Binary Sensor.
Switching to a trigger-based method will allow you to process the data in the variables block and eliminate the need to use this.
Instead of using a loop and multiple namespaces to do the comparison then paring down to the max or min, I would find the min and max temperatures of the set of forecasts before doing the comparison.
Yeah, I 100% agree with Didgeridrew. Trigger-based template sensors are more reliable and better suited for complex calculations. They preserve all values across HA restarts, avoiding the "unavailable --> available again" state and its associated issues; there's a clear distinction between previous state and freshly calculated vaues (in short: variable name or attribute name gives you the fresh value, while this.attributes.whatever - the old one).
I think this should illustrate main differences (regarding state and attributes)
attributes:
attr0: "{{ attr1 }}" // accessing attr1 before "declaration", always undefined
attr1: "{{ now().second }}" // "declaration" of attr1, each time returning a new value
attr2: "{{ this.attributes.attr1 }}" // returns an old/previous value of attr1
attr3: "{{ attr1 }}" // returns a freshly calculated value, attr3 equals attr1
state: "{{ attr1 }}" // always undefined, attr1 is not visible here, but variables are!