Problem with a complicated Jinja tempate

Hello,

I have a more complicated template problem: I get the signal strengths of my smartphone for different locations via ESP Home BLE sensors. I want to select the value with the lowest attenuation from these values. After a bit of trial and error, I’m almost at the solution:

  • Filter the relevant sensors via unit_of_measurement and state
  • Sort the sensors via sort

In the If loop

  • only consider the sensors that reported data in the last 32 seconds
  • Only consider values greater than -100

I am now missing the option of simply selecting the first value that represents the location of my smartphone. Unfortunately, I get a string from the if loop to which I can not apply a “|first” filter.

I currently list all results with a colon for testing purposes as one can see in the code below. I think the if loop is the problem, but I’m stuck now. Can someone help me, please?

{% for state in 
   ( states.sensor
     | selectattr('attributes.unit_of_measurement', 'defined')
     | selectattr('attributes.unit_of_measurement', 'eq', 'dBm')
     | selectattr('entity_id', 'search', 'presence_smartphone_johannes_rssi')
     | rejectattr('state', 'search', 'unavailable')
     | rejectattr('state', 'search', 'unknown')
     | sort(attribute='state')  
   )
-%}
  {% if 
       (now()-state.last_changed).total_seconds() < 32
     and
       state.state|float(0) > -100
  %}{{  
      (state.entity_id
        | default('not_home')
        | replace('sensor.','')
        | replace('_presence_smartphone_johannes_rssi','')
        | replace('_computer','')
        | replace('_desk_roof_slope','')
        | replace('_desk_wall','')
        )   
    }} ; {% endif -%}
{% endfor %}
{% set s_obj_list = states.sensor
  | selectattr('attributes.unit_of_measurement', 'defined')
  | selectattr('attributes.unit_of_measurement', 'eq', 'dBm')
  | selectattr('entity_id', 'search', 'presence_smartphone_johannes_rssi')
  | selectattr('state', 'is_number')
  | selectattr('last_changed', 'gt', now() - timedelta(seconds=32))
  | list %}

{% set min_dbm = s_obj_list 
  | map(attribute='state') 
  | map('int') 
  | select('<', -100) 
  | min 
  | default('not_home', 1) %}

{% if min_dbm == 'not_home' %}
  {{ min_dbm }}
{% else %}
  {{ s_obj_list 
    | selectattr('state', 'eq', min_dbm | string) 
    | map(attribute='entity_id') 
    | first 
    | replace('sensor.','')
    | replace('_presence_smartphone_johannes_rssi','')
    | replace('_computer','')
    | replace('_desk_roof_slope','')
    | replace('_desk_wall','') }}
{% endif %}
1 Like

This can be done in one go

  | regex_replace('sensor.|_presence_smartphone_johannes_rssi|_computer|_desk_roof_slope|_desk_wall', '')

I would also advice to apply a label to all these sensors and use that, instead of iterating over all sensors. You also won’t need a lot of these selectattr parts then, as you already know you have the right entities

With both suggestions applied @Didgeridrew’s template would become

{% set s_obj_list = label_entities('ble_sensors')
  | expand
  | selectattr('state', 'is_number')
  | selectattr('last_changed', 'gt', now() - timedelta(seconds=32))
  | list %}

{% set min_dbm = s_obj_list 
  | map(attribute='state') 
  | map('int') 
  | select('<', -100) 
  | min 
  | default('not_home', 1) %}

{% if min_dbm == 'not_home' %}
  {{ min_dbm }}
{% else %}
  {{ s_obj_list 
    | selectattr('state', 'eq', min_dbm | string) 
    | map(attribute='entity_id') 
    | first 
    | regex_replace('sensor.|_presence_smartphone_johannes_rssi|_computer|_desk_roof_slope|_desk_wall', '') }}
{% endif %}
3 Likes

Dear @Didgeridrew and @TheFes,

thank you very much - I am overwhelmed by the helpfulness and awesomeness of our community!

The solution worked almost straight away - I just had to change the greater than/less than sign because the numbers are negative: | select('>;', -100) Actually, I can just omit this filter since the state is properly mapped as an integer. :star_struck:

By the way: Is there any good tutorial for advanced jinja templating?

Edit: So that my question and the underlying project can be of use to others, here is the corresponding ESP code. With my solution, I achieve quite reliable detection of the location (assuming clever placement of the ESP boards in suitable corners of the rooms, sometimes even several boards in one room). The system reacts to a change of location within 1-3 seconds.

number:
  - platform: homeassistant
    id: rssi_factor
    entity_id: input_number.whatever_location #defaults to 100
  - platform: homeassistant
    id: rssi_factor_johannes
    entity_id: input_number.smartphone_johannes_rssi_factor #defaults to 100

bluetooth_proxy:
  active: true

esp32_ble_tracker:  
  scan_parameters: 
    interval: 150 ms
    window: 123 ms
    duration: 5 min

binary_sensor:
  - platform: ble_presence
    ibeacon_uuid: 'confidential'
    name: "smartphone_johannes"

sensor:
  - platform: ble_rssi
    ibeacon_uuid: 'confidential'
    name: smartphone_johannes_rssi
    force_update: true
    filters:
    filters:
      - exponential_moving_average:
          alpha: 0.1
          send_every: 7
          send_first_at: 3
      - lambda: return ((id(rssi_factor_johannes).state*x)/id(rssi_factor).state);
      - timeout: 13 s

Not really… the closest thing to an organized tutorial is Jinja for Ninjas . There’s also a Jinja Cheatsheet you can download. Unfortunately, both of them are showing their age. Since the HA devs and community are regularly adding template functions, there are often better/easier ways to do some of the examples shown in either source. For the most up-to-date list of available functions, the best source is still the Home Assistant Templating docs.

The forum community has been working on creating and indexing useful examples and explanations which can be found in the Cookbook Index.

2 Likes