Jinja code guru needed please - Average of *enabled* sensors

Been looking here for answers but can’t figure out how to express the algorithm in HA I need.

  • I wish to calculate an average temperature.
  • There are 5 sensors, “sensor.temp1”, “sensor.temp2”,…
  • Each sensor has an associated flag that the front end uses to allow the user to enable/disable that sensor: input_boolean.temp1enabled", “input_boolean.temp2enabled”,… using a button card.
  • I only want to include those sensors that are enabled (e.g. input_boolean.temp1enabled == true) in any calculation.
  • I want to calculate the avg of all enabled sensors
  • I want to calculate the max of any enabled sensor.

I know I can use min_max platform but that doesn’t allow me to mode on the enabled/disabled flag.

I’ve got this far:

  sensor:

    # AVERAGE OF ALL ROOM TEMP SENSORS
    
    - platform: template 
      sensors:


        house_temp_avg:
          friendly_name: house_temp_avg
          unit_of_measurement: "°C"
          device_class: temperature
          value_template: >
            {{ }}
        
        house_temp_max:
          unit_of_measurement: "°C"
          device_class: temperature
          value_template: >
            {{ }}

How might I finish this off please?

I started thinking of storing the sensor and its flag in a tuple and creating a list of tuples to traverse through, adding values to a zero initialised var, in order to calculate avg and of course compare against previous max value to get the max sensor name/value. But I can’t help feeling I’m overcomplicating things?

 sensor:
   # AVERAGE OF ALL ROOM TEMP SENSORS
   - platform: template 
     sensors:
       house_temp_avg:
         friendly_name: house_temp_avg
         unit_of_measurement: "°C"
         device_class: temperature
         value_template: >
           {% set a = 0 if is_state('input_boolean.temp1enabled', 'off') else states('sensor.temp1')|float %}
           {% set b = 0 if is_state('input_boolean.temp2enabled', 'off') else states('sensor.temp2')|float %}
           {% set c = 0 if is_state('input_boolean.temp3enabled', 'off') else states('sensor.temp3')|float %}
           {% set d = 0 if is_state('input_boolean.temp4enabled', 'off') else states('sensor.temp4')|float %}
           {% set e = 0 if is_state('input_boolean.temp5enabled', 'off') else states('sensor.temp5')|float %}
           {{ (a + b + c + d + e) / 5 }} 
       
       house_temp_max:
         unit_of_measurement: "°C"
         device_class: temperature
         value_template: >
           {% set a = 0 if is_state('input_boolean.temp1enabled', 'off') else states('sensor.temp1')|float %}
           {% set b = 0 if is_state('input_boolean.temp2enabled', 'off') else states('sensor.temp2')|float %}
           {% set c = 0 if is_state('input_boolean.temp3enabled', 'off') else states('sensor.temp3')|float %}
           {% set d = 0 if is_state('input_boolean.temp4enabled', 'off') else states('sensor.temp4')|float %}
           {% set e = 0 if is_state('input_boolean.temp5enabled', 'off') else states('sensor.temp5')|float %}
           {{ [a, b, c, d, e] | max }}
1 Like

@anon43302295 , many thanks. Wow. Great. One flaw though, the average is always taken on assumption that all 5 sensors are enabled, as in:

{{ (a + b + c + d + e) / 5 }}  ##- Always divided by 5 so not quite right. Needs to divide by count of enabled sensors.

So if for example a and d are disabled, the total of b, c, e is divided by 5 and not 3 (3 being the number of sensors enabled)

I was thinking of using tuples as per OP or maybe an array if dicts containing sensor state and sensor value key-pairs as in:

set arr=[ {off, 21.2} , {on, 19.1} , {on, 20.0} , {off, 19.5} , {on, 18.6} ]

Then I could count number of sensors that are enabled as in:

set sensors_enabled = (arr.keys == 'on')

And get averages using:

set avg_temp = avg(arr.values)

But the above isnt working (syntax errors) so I clearly haven’t grasped jinja/HA extensions. Is it even possible this way?

I suspect you could probably do some kind of for loop

Pseudo code (kotlin style)

set f = 0
set x = [a, b, c, d, e]
x.foreach {
  if(it != 0) f++
} 

Then

{{ (a + b + c + d + e) / f }}

But I can’t remember how to do that in jinja off the top of my head. I know scoping is a problem with for loops, but I’m sure someone will be able to point you in the right direction.

1 Like

Assuming you don’t have any other input booleans with the name “temp” then:

{% set count = namespace(value=0) %}
{% for state in states.input_boolean -%}
  {%- if state.name[0:4] == "temp" -%}
     {%- if state.state == "on" -%}
       {% set count.value = count.value + 1 %}
     {%- endif %}
  {%- endif %}
{%- endfor %}
{{ count.value }}

Where count.value will be the number of “on” temp booleans.

The explanation is that it loops all input_booleans and look at the name, if it starts with “temp” then it’s true.
Then it loks at the state, is it on., if true it adds one to the counter.

2 Likes

Thank you; you are generating ideas in my mind:
I am trying to use this but it’s not working, syntax all wrong I think

{% set arr= ( {"temp1":{"enabled":states("input_boolean.temp1enabled"),"temp":states("sensor.temp1")}}) 
{% for dict_item in arr %}
   {% for key, value in dict_item.items() %}
Key: {{key}}
Value: {{value}}
   {% endfor %}
{% endfor %}
#-- then somehow calculate the average and max values

Where arr is an array of dicts for each sensor although I’ve just used one sensor temp1 for example purposes. Can it be done?

Not like that I don’t think, I’m not that hot on jinja but I know you can’t generate key value pairs like that - but @Hellis81 's approach to get the count of enabled values is about right

for average

{%- set ns = namespace(entities = []) %}
{%- for i in range(1, 6) %}
  {%- set sensor_id = 'sensor.temp' ~ i %}
  {%- set boolean_id = 'input_boolean.temp' ~ i ~ 'enabled' %}
  {%- if is_state(boolean_id, 'on') %}
    {%- set ns.entities = ns.entities + [ expand(sensor_id) ] %}
  {%- endif %}
{%- endfor %}
{{ ns.entities | map(attribute='state') | map('float') | sum / ns.entities | count }}

It also could be done like this

{%- set ns = namespace(values = []) %}
{%- for i in range(1, 6) %}
  {%- set sensor_id = 'sensor.temp' ~ i %}
  {%- set boolean_id = 'input_boolean.temp' ~ i ~ 'enabled' %}
  {%- if is_state(boolean_id, 'on') %}
    {%- set ns.values = ns.values + [ states(sensor_id) | float ] %}
  {%- endif %}
{%- endfor %}
{{ ns.values | sum / ns.values | count }}

It’s basically iterating through the entities, if they are on it adds the value to a list. Then it sums the list up and divides it by the count. Both do the same thing. The first template returns a list of state objects, where you can get any information out of what is enabled. The second only puts the state value into the list as a floating number.

2 Likes

Thanks fellas. Sorry for late reply.
I went with @petro solution with a few adjustments as I grouped the sensors in the yaml config and in the for…loop I iterate over the group sensor names.

Excellent and works !

I also noted the template editor on lovelace Developer Tools, and clicked the links to the jinja designer page and HA extensions; been reading them to understand the code snippets you all provided. map and expand are useful functions👌

THANK YOU - solved!

1 Like

@petro , you seem to be an enlightened guru. May I inquire with you please as to if (and how) my config can be improved?
I have this config below.

  • essentially, I have grouped my sensors as declared below,
  • I use template code to iterate over the sensors contained within the group
  • map() is used to extract the entity_ids of each sensor within the group
  • a filter removes the preceding component/integration domain name to yield just the id part of the entity.
  • the id is used to construct the input_boolean entity ids and the sensor ids.
  • if the corresponding input_boolean - for a given sensor name - is ‘on’ then the sensor entity state value is used and the average mean and the max values are calculated.
...
  group:

    room_temperature_sensors:
      name: Room temp sensors
      entities:
        - sensor.ble_temperature_bedroom
        - sensor.ble_temperature_lounge
        - sensor.ble_temperature_upper
        - sensor.ble_temperature_kitchen
        - sensor.ble_temperature_guest          
...

  sensor:

    - platform: template 
      sensors:

        house_temp_avg:
          friendly_name: "House temp avg."
          unit_of_measurement: "°C"
          value_template: >
            {%- set ns = namespace(values = []) %}
            {%- for sensor_entity_ids in expand('group.room_temperature_sensors') | map(attribute='entity_id') %}
              {%- set sensor_name = sensor_entity_ids|regex_replace('.*\.','') %}
              {%- set enabled_id = 'input_boolean.' ~ sensor_name ~ '_enabled' %}
              {%- if is_state(enabled_id, 'on') %}
                {%- set sensor_id = 'sensor.' ~ sensor_name %}
                {%- set ns.values = ns.values + [ states(sensor_id) | float ] %}
              {%- endif %}
            {%- endfor %}
            {{ ns.values | sum / ns.values | count }}

        house_temp_max:
            friendly_name: "House temp max."
            unit_of_measurement: "°C"
            value_template: >
              {%- set ns = namespace(values = []) %}
              {%- for sensor_entity_ids in expand('group.room_temperature_sensors') | map(attribute='entity_id') %}
                {%- set sensor_name = sensor_entity_ids|regex_replace('.*\.','') %}
                {%- set enabled_id = 'input_boolean.' ~ sensor_name ~ '_enabled' %}
                {%- if is_state(enabled_id, 'on') %}
                  {%- set sensor_id = 'sensor.' ~ sensor_name %}
                  {%- set ns.values = ns.values + [ states(sensor_id) | float ] %}
                {%- endif %}
              {%- endfor %}
              {{ ns.values | max }}
  
        house_temp_midpoint:
          friendly_name: "house temp midpoint"
          unit_of_measurement: "°C"
          value_template: "{{ (states('sensor.house_temp_max') | float + states('sensor.house_temp_avg') | float) / 2.0  }}"
          

Two questions if I may.

  1. Is there a better more efficient way of doing this?
  2. Is there a way to use “a template within a template”. What I mean is that value_template code is almost identical for the avg and max sensor values, with the exception of the final lines in each, so is there a way to code up the common code as a single block , call this block inside each of the template blocks, then use the final lines as in:
           {{ ns.values | sum / ns.values | count }}

and

            {{ ns.values | max }}

Incidentally, this is all working perfectly so thank you