[Template Average Sensor] Please help me improve it

Hey guys,

I have a template sensor to get an average of some temperature sensors:

    average_office:
      friendly_name: "Average Office"
      device_class: temperature
      icon_template: "mdi:thermometer"
      unit_of_measurement: "°C"
      value_template: >-
        {{ ((states('sensor.aqara_office_temp') | float +
        states('sensor.aqara_office2_temp') | float ) /
        2) | round(2) }}

The problem: when I update deCONZ - or the sensors become unavailable for another reason - it jumps to 0. If only one sensor gets unavailable it halves the available one.

Is there any way I can circumvent this? Should I look into the statistics sensor?

Thanks a lot in advance!

2 Likes

The statistics integration is for one sensor. Use the min_max integration (it does average of multiple sensors too).

sensor:
  - platform: min_max
    type: mean
    name: "Average Office"
    entity_ids:
      - sensor.aqara_office_temp
      - sensor.aqara_office2_temp

If that does the same thing we’ll have go back to your template sensor and use an availability template to reject unknown/unavailable values.

availability_template: >
  {{ 0 not in [ states('sensor.aqara_office_temp')|float, states('sensor.aqara_office2_temp')|float ] }}

That is simple but requires your office temperature to never be 0°C. If that is possible there’s a more complex availability template you can use for the template sensor:

availability_template: "{{ states('sensor.aqara_office_temp') not in [ 'Unavailable', 'None', 'Unknown' ] and states('sensor.aqara_office2_temp') not in [ 'Unavailable', 'None', 'Unknown' ] }}"
1 Like

Try this:

    average_office:
      friendly_name: "Average Office"
      device_class: temperature
      icon_template: "mdi:thermometer"
      unit_of_measurement: "°C"
      value_template: >-
        {% set t1 = states('sensor.aqara_office_temp') %}
        {% set t2 = states('sensor.aqara_office2_temp') %}
        {% if t1 != 'unavailable' and t2 != 'unavailable' %}
          {{ ((t1 | float + t2 | float ) / 2) | round(2) }}
        {% else %}
          {{ states('sensor.average_office') }}
        {% endif %}
  • If both temperature values are not unavailable then it reports their average.

  • If either temperature value is unavailable then it reports the existing value of sensor.average_office.

2 Likes

Thank you very much, Tom!

Just tried the min_max integration and simulated both sensors to be unavailable - the result is what I am looking for (NaN in the mini graph card - so it doesn’t mess up the history)! :slight_smile:

I’ll also add Taras’ variant and test the behaviour if only one sensor becomes unavailable. Will post the results here.

Hey Taras,

Also big thanks to you - very appreciated!

I tried both variants and both are actually great solutions with different “pros and cons”. With some small additions to your code, I was able to get the best of both:

    average_office:
      friendly_name: "Average Office"
      device_class: temperature
      icon_template: "mdi:thermometer"
      unit_of_measurement: "°C"
      value_template: >-
        {% set t1 = states('sensor.aqara_office_temp') %}
        {% set t2 = states('sensor.aqara_office2_temp') %}
        {% if t1 != 'unavailable' and t2 != 'unavailable' %}
          {{ ((t1 | float + t2 | float ) / 2) | round(2) }}
        {% elif t1 == 'unavailable' and t2 != 'unavailable' %}
          {{ (( t2 | float )) | round(2) }}
        {% elif t1 != 'unavailable' and t2 == 'unavailable' %}
          {{ (( t1 | float )) | round(2) }}
        {% else %}
          {{ states('sensor.average_office') }}
        {% endif %}

It will still update when t1 is unavailable but t2 has changed and vice-versa. But also keeps the last value in case both sensors go unavailable.

PS: @tom_l & @123 absolutely love what you guys contribute to this community! I learn(ed) so much from you both.

1 Like

I didn’t realize you wanted it to work that way. In that case, this template achieves that goal:

    average_office:
      friendly_name: "Average Office"
      device_class: temperature
      icon_template: "mdi:thermometer"
      unit_of_measurement: "°C"
      value_template: >-
        {% set t = [states('sensor.aqara_office_temp'), states('sensor.aqara_office2_temp')]
                   | reject('eq', 'unavailable') | map('float') | list %}
        {% set qty = t|count %}
        {{ (t|sum/qty)|round(2) if qty > 0 else states('sensor.average_office') }}

4 Likes

I didn’t know either at the time of posting this :smiley:

Thanks a ton, this is perfect!

1 Like

This works wonderfully for me, however it results into all sorts of errors in my HA logs:

2022-03-17 21:09:12 WARNING (MainThread) [homeassistant.helpers.template] Template warning: 'float' got invalid input 'unknown' when rendering template '{% set t = [states('sensor.livingroom_temperature_humidity_sensor_temperature'), states('sensor.diningroom_temperature_humidity_sensor_temperature'), states('sensor.kitchen_temperature_humidity_sensor_temperature'), states('sensor.netatmo_thermostat_temperature') ] | reject('eq', 'unavailable') | map('float') | list %} {% set qty = t|count %} {{ (t|sum/qty)|round(1) if qty > 0 else states('sensor.netatmo_thermostat_temperature') }}' but no default was specified. Currently 'float' will return '0', however this template will fail to render in Home Assistant core 2022.1

There are several discussions here on how to declare ‘float’ (by adding 0), but that doesnt seem to make a difference. Am I misunderstanding something?

Show us how you tried to fix it.

- sensor:
    - name: "Average Temperature Downstairs"
      unique_id: sensor.average_temperature_downstairs
      unit_of_measurement: "°C"
      device_class: temperature
      state: >
        {% set t = [states('sensor.livingroom_temperature_humidity_sensor_temperature'), states('sensor.diningroom_temperature_humidity_sensor_temperature'), states('sensor.kitchen_temperature_humidity_sensor_temperature'), states('sensor.netatmo_thermostat_temperature') ] | reject('eq', 'unavailable') | map('float**(0)**') | list %}
        {% set qty = t|count %}
        {{ (t|sum/qty)|round(1) if qty > 0 else states('sensor.netatmo_thermostat_temperature') }}```

But after the restart, the same logs continue to pop-up, now saying:

Unable to parse temperature sensor sensor.average_temperature_downstairs with state: unavailable

This appears to be acceptable:

map('float', 0)

No warnings were logged for map('float', 0) whereas I detected them when using map('float').

2 Likes

On restart you’re still going to get the error because your else (sensor.netatmo_thermostat_temperature) is unavailable or unknown at restart.

Setting an availability can help with those restart errors. Also, the average filter has been added since the original post so you don’t need to set up the math anymore:

- sensor:
    - name: "Average Temperature Downstairs"
      unique_id: sensor.average_temperature_downstairs
      unit_of_measurement: "°C"
      device_class: temperature
      state: >
        {{ expand('sensor.livingroom_temperature_humidity_sensor_temperature', 
        'sensor.diningroom_temperature_humidity_sensor_temperature',                         
        'sensor.kitchen_temperature_humidity_sensor_temperature', 
        'sensor.netatmo_thermostat_temperature')
        | rejectattr('state', 'in', ['unknown','unavailable'])
        | map(attribute='state') | map('float', 0) | average | round(2) }}
      availability: >
        {{ expand('sensor.livingroom_temperature_humidity_sensor_temperature', 
        'sensor.diningroom_temperature_humidity_sensor_temperature',                         
        'sensor.kitchen_temperature_humidity_sensor_temperature', 
        'sensor.netatmo_thermostat_temperature')
        | rejectattr('state', 'in', ['unknown','unavailable']) | list | count > 0}}
2 Likes

Do I understand correctly here in that in state one is declaring the different entities for the different temperatures, and that in availability the conditions for their possible being unavailable are being set?

the suggested code appears to “validate” but gives quite the error on restarting:

  File "/usr/src/homeassistant/homeassistant/components/hassio/__init__.py", line 610, in async_handle_core_service
    raise HomeAssistantError(
homeassistant.exceptions.HomeAssistantError: The system cannot restart because the configuration is not valid: Error loading /config/configuration.yaml: while parsing a flow mapping
  in "/config/templates.yaml", line 24, column 10
expected ',' or '}', but got '<scalar>'
  in "/config/templates.yaml", line 27, column 48

Yes.

I missed the multiline quote symbol when I c/p the template into the availability. I have edited it in original post.

1 Like

What I ended up with is:

{{ expand(area_entities('Nappali'))
  |selectattr('attributes.device_class', 'defined')
  |selectattr('attributes.device_class', 'eq', 'temperature')
  |rejectattr('state', 'in', ['unavailable', 'unknown', 'none'])
  |map(attribute='state')|map('float')|list|average|round(1) }}

So this averages every temperature sensor reporting data in a particular area (room).
Also, it’s relatively easy to join rooms or even looking at a whole floor by adding more rooms, e.g. {{ expand(area_entities('Room1'), area_entities('Room2')) | selectattr...

In hope that I don’t ever have to touch this filter again haha :slight_smile:

2 Likes

Well, I don’t want to curb your enthusiasm, but I tried it and found it nice until discovering that a lot of other things have temperatures: cpu of my computer, settings of the thermostat, batteries, etc.

You haven’t (at least mine), that’s a |rejectattr('entity_id', 'search', '.*device_?temperature.*|sensor.(ups_.*)') type throw.

The more difficult thing I ran into however is when two types of devices are reporting temperatures that are of interest. In the above example, I’m looking for device_class temperature, but there’s domain climate as well for TRV devices. That takes some stitching, but still, much simpler for my brain than anything I came across so far.

Stg like this:

          {% set room=expand(area_entities('Nappali')) %}
          {% set sensors=room |selectattr('attributes.device_class', 'defined')
                              |selectattr('attributes.device_class', 'eq', 'temperature')
                              |rejectattr('entity_id', 'search', '.*device_?temperature.*|sensor.(ups_.*)')
                              |rejectattr('state','in', ['unknown', 'unavailable']) 
                              |map(attribute='state')|map('float')|list %}
          {% set radiators=room |selectattr('domain', 'eq', 'climate')
                                |selectattr('attributes.hvac_modes', 'defined')
                                |selectattr('entity_id', 'search', '.*_becathermostat')
                                |rejectattr('state','in', ['unknown', 'unavailable']) 
                                |map(attribute='attributes.current_temperature')|map('float')|list%}
          {{ (sensors+radiators)|average|round(1) }}

Hello! I have spent many sleepless hours trying to correct these “easy” little template sensors.

What I really wanted to accomplish was a weighted average between my thermostat and some sensors. I wish I could have just grouped them with a combined state and used an arithmetic mean.

I have a thermostat in the 2nd floor hallway of my older home, an area where there are no radiators. Since the hallway (actual) thermostat gets warm air from downstairs, it is always a little warmer than the bedrooms and rarely kicks on, always leaving the bedrooms and bathroom consistently cold. I wanted to give that thermostat less importance than the sensors in the cold rooms.

I came up with the following, thanks in big part to this post. I am 99% sure these are working for both the old and new way of templating.

Old Way Sensors:

sensor:
  - platform: template
    sensors:

      first_floor_temperature:
        friendly_name: "First Floor Temperature"
        unit_of_measurement: "°F"
        value_template: >
          {% set t1 = states('sensor.dining_room_thermostat_temperature') | float %}
          {% set t2 = states('sensor.living_room_temperature_lumi') | float %}
          {% if t2 != 'unavailable' %}
            {{ (t1 * 0.4 + t2 * 0.6) | round(1) }}
          {% else %}
            {{ states('sensor.dining_room_thermostat_temperature') | round(1) }}
          {% endif %} 

      second_floor_temperature:
        friendly_name: "Second Floor Temperature"
        unit_of_measurement: "°F"
        value_template: >
          {% set t1 = states('sensor.stairway_thermostat_temperature') | float %}
          {% set t2 = states('sensor.bedroom_temperature_lumi') | float %}
          {% set t3 = states('sensor.bathroom_temperature_lumi') | float %}
          {% if t2 != 'unavailable' and t3 != 'unavailable' %}
            {{ (t1 * 0.2 + t2 * 0.5 + t3 * 0.3) | round(1) }}
          {% elif t2 == 'unavailable' or t3 != 'unavailable' %}
            {{ (t1 * 0.45 + t3 * 0.55) | round(1) }}
          {% elif t2 != 'unavailable' and t3 == 'unavailable' %}
            {{ (t1 * 0.4 + t2 * 0.6) | round(1) }}
          {% else %}
            {{ states('sensor.stairway_thermostat_temperature') | round(1) }}
          {% endif %}

New Way Sensors:

- sensor:
        name: First Floor Temperature
        unit_of_measurement: "°F"
        state: >
          {% set t1 = states('sensor.dining_room_thermostat_temperature') | float %}
          {% set t2 = states('sensor.living_room_temperature_lumi') | float %}
          {% if t2 != 'unavailable' %}
            {{ (t1 * 0.4 + t2 * 0.6) | round(1) }}
          {% else %}
            {{ states('sensor.dining_room_thermostat_temperature') | round(1) }}
          {% endif %} 
- sensor:
        name: Second Floor Temperature
        unit_of_measurement: "°F"
        state: >
          {% set t1 = states('sensor.stairway_thermostat_temperature') | float %}
          {% set t2 = states('sensor.bedroom_temperature_lumi') | float %}
          {% set t3 = states('sensor.bathroom_temperature_lumi') | float %}
          {% if t2 != 'unavailable' and t3 != 'unavailable' %}
            {{ (t1 * 0.2 + t2 * 0.5 + t3 * 0.3) | round(1) }}
          {% elif t2 == 'unavailable' or t3 != 'unavailable' %}
            {{ (t1 * 0.45 + t3 * 0.55) | round(1) }}
          {% elif t2 != 'unavailable' and t3 == 'unavailable' %}
            {{ (t1 * 0.4 + t2 * 0.6) | round(1) }}
          {% else %}
            {{ states('sensor.stairway_thermostat_temperature') | round(1) }}
          {% endif %}

I used M1ke’s code to get me started, but I found one big thing missing. Every time I ran it, I got an error. The solution was to add a float to each of the defined states. Otherwise the formulas were just looking at text and not trying to calculate integers. I hope this makes sense.

Returns an Integer:

{% set t1 = states('sensor.dining_room_thermostat_temperature') | float %}

Returns Text:

{% set t1 = states('sensor.dining_room_thermostat_temperature') %}

I would love to hear any suggestions on my finally working code, and I hope that my examples can help someone in the future.