Get two values from custom template sensors / global jinja variables

Hello,

I’m trying to create a sensor that shows dates from the next three days, where the temperature drops below a defined level. I need two values, both the values for the date and the corresponding temperature.

For that I’ve created an input_number slider with which I can set the lower level and where I compare the forecast against.

I’ve also created a template sensor like so:

min_temp_next_3d_namespace:
  friendly_name: Min temperature
  value_templates: >
      {% set temperature = namespace(min=states('input_number.weather_limit')|float, date=now(), trigger=false) %}
      {%- for item in state_attr('weather.home', 'forecast')[:3] -%}
        {% if ((item.templow | float) < temperature.min) %}
          {% set temperature.trigger = true %}
          {% set temperature.min = (item.templow | float) %}
          {% set temperature.date = item.datetime %}
        {% endif %}
      {% endfor %}
      {{ temperature }}

In doing this, I get a string of the namespace from which I obviously can’t access the values.

I could do it with attribute_templates but I don’t know how I would avoid having to use the above algorithm within each attributes block. I don’t know of a way to globally define temperature so that I can access it within the attributes blocks.

Any hints or ideas how to implement this?

Thanks in advance!

Hi again,

I’m not having any luck with my threads, the last three went unanswered.

Anyway, I went with a workaround of defining a second sensor that gets the temperature from the forecast if the date from the first sensor matches:

First sensor (slightly altered):

min_temp_next_3d_date:
  value_template: >
    {% set temperature = namespace(date="-") %}
    {%- for item in state_attr('weather.home', 'forecast')[:3]|reverse -%}
      {% if ((item.templow | float) < states('input_number.weather_limit')|float) %}
        {% set temperature.date = item.datetime %}
      {% endif %}
    {% endfor %}
    {{temperature.date}}

Second sensor:

min_temp_next_3d_temp:
  value_template: >
    {% set temperature = namespace(temp="-") %}
    {%- for item in state_attr('weather.home', 'forecast')[:3] -%}
      {% if as_timestamp(states('sensor.min_temp_next_3d_date')) | timestamp_custom('%Y-%m-%d') == as_timestamp(item.datetime) | timestamp_custom('%Y-%m-%d') %}
        {% set temperature.temp = item.templow %}
      {% endif %}
    {% endfor %}
    {{temperature.temp}}
  unit_of_measurement: "°C"

Works like this:

What if you change the last line of your first example to this:

{{ temperature.min }} {{ temperature.date }}

The result will be something like this:

-1.0 2021-04-30T07:43:02.618237-04:00

The individual values can be acquired using the split() function.

The template you use in one option cannot be accessed by another option. Therefore if you were to create temperature and date as attributes, each one would need its own template (i.e. you would have to duplicate the template).

Thanks, that would work and is a probably a better solution than running that for loop again.

In addition with your last paragraph I would consider this as solved, thanks!

1 Like

If you wish you can more the if statement into the criteria for the for statement like this:

{% set temperature = namespace(min=states('input_number.weather_limit')|float, date=now(), trigger=false) %}
{%- for item in state_attr('weather.home', 'forecast')[:3] if item.templow | float < temperature.min %}
    {% set temperature.trigger = true %}
    {% set temperature.min = (item.templow | float) %}
    {% set temperature.date = item.datetime %}
{% endfor %}
{{ temperature.min }} {{ temperature.date }}

It reduces the number of iterations the for loop must perform.

BTW, is the purpose of the entire for loop simply to find the lowest value of templow?


If the goal is to get the lowest temperature in the next three days, and its date, you can do it like this:

min_temp_next_3d_date:
  friendly_name: Low Temperature Date
  device_class: timestamp
  value_template: >
    {% set f = state_attr('weather.home', 'forecast')[:3] %}
    {% set m = f | map(attribute='templow') | min %}
    {{ f | selectattr('templow', 'eq', m) | map(attribute='datetime') | join }}

min_temp_next_3d_temp:
  friendly_name: Lowest Low Temperature
  device_class: temperature
  unit_of_measurement: '°C'
  value_template: >
    {% set f = state_attr('weather.home', 'forecast')[:3] %}
    {{ f | map(attribute='templow') | min }}

Thanks for the tip with the if statement in the for loop.

I’m trying to get the next time the temperature will drop below an input_number if at all within the next three days.

Using your example with the map function which I didn’t know of I changed my loop to this (written explicitly verbosely):

{% set forecast_next_three_days = state_attr('weather.home', 'forecast')[:3] %}

{% set low_temps = forecast_next_three_days | selectattr('templow', 'le', states('input_number.weather_limit')|float) %}

{% set date_times = low_temps | map(attribute='datetime') %}


{% if date_times %}
 {{ date_times | min }}
{% else %}
  None
{% endif %}

Is it possible to implement the check if the date_times list is empty somehow without the if else statement? As some sort of filter? Because that way I could write it all in one line, like this:
{{ state_attr('weather.home', 'forecast')[:3] | selectattr('templow', 'le', states('input_number.weather_limit')|float) | map(attribute='datetime') | min }}

You can’t because if min is given no data (i.e. no temperatures are less than or equal to weather_limit) it will generate an error message:

ValueError: min() arg is an empty sequence

The alternative is two lines:

{% set x = state_attr('weather.home', 'forecast')[:3] | selectattr('templow', 'le', states('input_number.weather_limit')|float) | map(attribute='datetime') | list %}
{{ x|min if x else 'None' }}

However, if you use this in a Template Sensor with device_class: timestamp, I don’t know what it will do when the value is ‘None’.

ValueError: min() arg is an empty sequence

yes, that’s why I currently have the if/else implemented. I’ll use two lines then. Thanks!