Use template for binary_sensor threshold?

Thanks seanomat. I tried that out and handles the upper and lower thresholds. I’m going to modify it to be a sensor by adding the logic to have above, in_range, and below as the values returned vs just on and off. I’m controlling a fan to help offload my AC and I need to know all 3 states to turn it on and off properly (for instance if it is out of range above, I want the fan on, but If it is out of range below, the fan can’t heat so I don’t want to turn it on.) With your code it was easy to make it a sensor and it seems to work in the template editor. I need to collect some data and see if it turns on and off properly. I’m not sure if using two single ended binary sensors or this single sensor is best. Gotta try it. Thanks for your help with this.

I case if your fan you could have stayed with the original template with just the upper limit, as you only need two states: ON and OFF.

I think, there is still an error in the last template.

It should be something like this (not tested!):

{% set threshold_upper = limit_upper - (hysteresis if (current_state=='off' and referenced_state>(limit_upper - hysteresis)) else -hysteresis)  %}
{% set threshold_lower = limit_lower + (hysteresis if (current_state=='off' and referenced_state<(limit_lower + hysteresis)) else -hysteresis)  %}

I came to the same conclusion to use a single threshold. I modified your code to this and it seems to be working.

{% set entity=states('sensor.outside_temperature')|float(0) %}
{% set hysteresis=states('input_number.inside_temp_hysteresis')|float(0) %}
{% set limit=states('sensor.inside_temperature')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
{{ entity < limit }}

Thanks for your help

2 Likes

@seanomat I tested this for a while now and it works, but I wanted to do a modification but could not get the modification working.

Currently, its works as follows, assuming an inside temp of 21°C and hysteresis of 0.5°C
Trigger to off at 21.5°C (rising)
Trigger to on at 20.5°C (subsiding)

My goal is to
Trigger to off (risig temp) at 20.5°C
and
Trigger to on at 21.5°C (subsiding temp)
so exactly the other way around, switching states before it reaches the actual temperature.

However it looks like I’m missing something. I thought I would just flip the hysteresis algebraic signs, but that does for whatever reason not work. I also tried other variants, but no luck.
Here is the snippet of what I think should work, but doesn’t.

{% set limit=states('sensor.home_temperature')|float(0) + (-hysteresis if this.state=='on' else +hysteresis)  %}
{{ entity < limit }}

can anyone help?

A threshold is meant to avoid flapping (frequent state changes). So the state change has to occur at the “further” limit in regard to the direction the underlying sensor is moving and there is an overlap in the middle.

What you want is the direct opposite, so this will not work.

Say the temp is 20, the state is on, the limit is 20.5.
The temp rises to 21, your limit has been reached, so the state is off, but since it’s off the limit now changes to 21.5.
Since the state is defined as entity< limit the state changes to on again. Which changes the limit again, and so on.

It seems to me you are making this more complicated then necessary.

My use case for this FR:

    name: "Sun Azimuth SE"
    unique_id: sun_azimuth_se
    lower: '{{ states("input_number.azimuth_se_lower") }}'
    upper: '{{ states("input_number.azimuth_se_upper") }}'
    entity_id: sensor.sun_azimuth

  - platform: threshold
    name: "Sun Azimuth South"
    unique_id: sun_azimuth_south
    lower: '{{ states("input_number.azimuth_south_lower") }}'
    upper: '{{ states("input_number.azimuth_south_upper") }}'
    entity_id: sensor.sun_azimuth
    
  - platform: threshold
    name: "Sun Azimuth SW"
    unique_id: sun_azimuth_sw
    lower: '{{ states("input_number.azimuth_sw_lower") }}'
    upper: '{{ states("input_number.azimuth_sw_upper") }}'
    entity_id: sensor.sun_azimuth

rather than:

- binary_sensor:    

  - name: "Sun Azimuth SE"
    unique_id: sun_azimuth_se
    state: '{{ states( "input_number.azimuth_se_lower") < states("sensor.sun_azimuth") < states( "input_number.azimuth_se_upper") }}'

  - name: "Sun Azimuth South"
    unique_id: sun_azimuth_south
    state: '{{ states( "input_number.azimuth_south_lower" ) < states("sensor.sun_azimuth") < states( "input_number.azimuth_south_upper" ) }}'
   
  - name: "Sun Azimuth SW"
    unique_id: sun_azimuth_sw
    state: '{{ states( "input_number.azimuth_sw_lower" ) < states("sensor.sun_azimuth") < states( "input_number.azimuth_sw_upper" ) }}'

Also would like to see unique_id: added to threshold sensors.

you are absolutely right, my misjudgment…
I have kind of a workaround by adding + 1°C to the environment temperature, rather than playing wronlgy with the hysteresis +/-.

          {% set entity=states('sensor.environment_temperature')|float(0) + 1 %}

That works kind of, it triggers sooner in the morning, however also later in the evening. I could create two sensors, one with and one without that “+1°C”, and then combine the two with an automation, but ideally I wanted to create only one sensor. :slight_smile:

I’m not that advanced with the binary-sensors, is it possible to trigger the binary sensor from on to off when one condition (environment +1°C) would fire a on-to-off event (in the morning), and to trigger from off to on if the second condition (environment +0°C) fires off-to-on event (in the evening)?

I have illustrated this in the 3rd line on the below graphic

The code for the first line/sensor

state: |
          {% set entity=states('sensor.environment_temperature')|float(0) %}
          {% set hysteresis=states('input_number.environment_cold_diff')|float(0) %}
          {% set limit=states('sensor.home_temperature')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
          {{ entity < limit }}

The code for the second line/sensor

state: |
          {% set entity=states('sensor.environment_temperature')|float(0) + 1 %}
          {% set hysteresis=states('input_number.environment_cold_diff')|float(0) %}
          {% set limit=states('sensor.home_temperature')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
          {{ entity < limit }}

You don’t need a hysteresis in your case:

{% set temp_home = states('sensor.home_temperature')|float(0) %}
{% set temp_env = states('sensor.environment_temperature')|float(0) %}
{% set diff = 0.5 %}
{{ (temp_env >= temp_home + diff ) or (temp_env <= temp_home - diff ) }}

This sensor will be on above 21.5 and below 20.5 provided your home_temperature is 21.0.
You might have to switch this around for your case. I am a littel confused about what you really want.

I saw your post from yesterday but you have deleted it :smiley: anyway, I found a way now which works fabulous for my requirement (I definitely need the threshold, as you mentioned in your deleted post).

I have modified your sensor a bit, adding an offset to the outside temp. The threshold is set to 1°C in my case.

- binary_sensor:
      - name: Inside vs outside temp
        icon: mdi:thermometer-low
        availability: |
          {{ not false in
             [states('sensor.outside_temp),
              states('input_number.threshold'),
              states('sensor.inside_temp')]|map('is_number')
          }}
        state: |
          {% set hysteresis=states('input_number.threshold')|float(0) %}
          {% set entity=states('sensor.outside_temp')|float(0) + states('input_number.offset')|float(0)  %}
          {% set limit=states('sensor.inside_temp')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
          {{ entity < limit }}

For the offset-temperature, I have created an automation, it includes also a safety margin (x2.5 instead of x2). In the morning, the offset in my case is at +2°C, which increases the outside temp artificially. In the evening, the offset is at -1°C, which decreasees the outside temp artificially.
Since there is the threshold in the binary sensor, you dont have any flapping.

alias: Offset on/off
description: ''
trigger:
  - platform: template
    value_template: >-
      {{ (states('sensor.outside_temp')|float(0) -
      states('sensor.inside_temp')|float(0)) > (2.5 *
      states('input_number.threshold')|float(0)) }}
    id: off
  - platform: template
    value_template: >-
      {{ (states('sensor.inside_temp')|float(0) -
      states('sensor.outside_temp')|float(0)) > (2.5 *
      states('input_number.threshold')|float(0)) }}
    id: on
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: off
        sequence:
          - data_template:
              value: >-
                {{ states('input_number.threshold')|float(0) *
                -1 }}
            entity_id: input_number.offset
            service: input_number.set_value
      - conditions:
          - condition: trigger
            id: on
        sequence:
          - data_template:
              value: >-
                {{ states('input_number.threshold')|float(0) *
                2 }}
            entity_id: input_number.offset
            service: input_number.set_value
    default: []
mode: single

The only problem was, that if the outside temperature does not go below the inside temp for the defined [offset-threshold], the offset will remain as it was. For this, I have created an additional trigger at 5 AM in the above automation, which will check this, including a bit of safety margin (0.5°C):

      - conditions:
          - condition: trigger
            id: time
          - condition: template
            value_template: >-
              {{ (states('sensor.inside_temp')|float(0) -
              states('sensor.outside_temp')|float(0)) <= (2.5 *
              states('input_number.threshold')|float(0)) }}
          - condition: state
            entity_id: binary_sensor.invise_vs_outside_temp
            state: 'on'
        sequence:
          - data_template:
              value: >-
                {{ (states('sensor.inside_temp')|float(0)
                - states('sensor.outside_temp')|float(0) +
                states('input_number.threshold')|float(0) +
                0.5)|round(1) }}
            entity_id: input_number.offset
            service: input_number.set_value

This is the result (the offset would change dynamically at 5AM in this scenario, you can see inside vs outside temp was almost the same during the night):

Cheers and thanks for the help and input!

Yeah, turned out my last post created the exact loop that I described a few posts above :thinking:

Yes, I guess you could solve the problem with an automation. I was hoping to to avoid that.

Ideally the automation can be eliminated, but for now, I’m good. One could probably pack the rules for the offset into the binary sensor too, however when the outside temp is close to the inside temp on certain days, that will require the “5 AM fix” :smile:

ok, so I think I’ve finally got it. Still have to observer though.
The logic is this:
You have three phases:

  1. below lower limit
  2. between lower limit and upper limit
  3. above upper limit.

Phase 1. and 3. are easy.
Phase two has two cases:
2.1. value crosses the border from outside to inside
2.2. value changes within the interval

In case 2.1. the state should toggle, no matter if it is coming from above (OFF to ON) or below (ON to OFF)
In Case 2.2 the state should stay the same it was after entering the interval, until it leaves the interval again, in which case we would be in Phase 1. or 3. again.

To achieve this we need to know, weather the previous value was inside the interval or not.
So I introduced an additional state attribute “isin” that stores exactly that.

This is what it looks like:

        state: |
          {% set current_state = this.state %}
          {% set base_entity = states('sensor.environment_temperature')|float(0) %}
          {% set limit_upper = states('sensor.home_temperature')|float(0) + states('input_number.home_temperature_warm_diff')|float(0) %}
          {% set limit_lower = states('sensor.home_temperature')|float(0) - states('input_number.home_temperature_warm_diff')|float(0) %}
          {% if base_entity > limit_upper %}
            on
          {% elif base_entity < limit_lower %}
            off
          {% else %}
            {% if state_attr(current_state,'isin') == 'on' %}
              {{ this.state }}
            {% else %}
              {{ not this.state }}
            {% endif %}
          {% endif %}
        attributes:
          isin: |
            {% set base_entity = states('sensor.environment_temperature')|float(0) %}
            {% set limit_upper = states('sensor.home_temperature')|float(0) + states('input_number.home_temperature_warm_diff')|float(0) %}
            {% set limit_lower = states('sensor.home_temperature')|float(0) - states('input_number.home_temperature_warm_diff')|float(0) %}
            {{ limit_lower <= base_entity <= limit_upper }}

[EDIT 20220818] Removed Quotes from “on” and “off”

that looks great too, let me try it out, will observe it for a while and report back

It works, but without hysteresis it might be flapping

the hysteresis in your code above is 'input_number.home_temperature_warm_diff' if I got it correctly?

There is no hysteresis in the classical sense.
This still needs to be added.
In fact, there need to be two now, for both limits.

So ‘input_number.home_temperature_warm_diff’ is an offset only?

I also need a threshold binary sensor and use the helper for this. In my case I need a variable lower limit with a fix threshold. First, I can not enter the variable lower direct into the helper GUI, its red and does not work.

Then I tried following in my templates.yaml file:

sensor:    
  - name: "Pool IST Temp"
    state: >
      {{ states('input_number.poolactualtemp') }}
    
binary_sensor:
  - platform: threshold
    name: "Pool Need Heat"
    entity_id: sensor.pool_ist_temp
    lower: {{ states(input_number.poolnominaltemp1) }}
    hysteresis: 0.5

Also doesn’t work.

And when I set a number for “lower”, quick restart is possible but after that with following error messages:

sensor:
  - name: "Pool IST Temp"
    state: >
      {{ states('input_number.poolactualtemp') }}
    
binary_sensor:
  - platform: threshold
    name: "Pool Need Heat"
    entity_id: sensor.pool_ist_temp
    lower: 30
    hysteresis: 0.5

Hopefully somebody can help
Thanks

You can see a working example above already in my post Use template for binary_sensor threshold? - #21 by fair_dinkum
The syntax changed slightly, so that since HA 2022.05 or 2022.06 this sensor must now be declared as a template sensor, see below.
My example uses an input helper for the threshold, so I can set a threshold directly in the UI - to be honest, I never changed it so far, but in case I would need to, there is an easy way to do so :smiley: I have been using this sensor for almost 2 years and it has been working great.

template:
- binary_sensor:
      - name: Inside vs outside temp
        icon: mdi:thermometer-low
        availability: |
          {{ not false in
             [states('sensor.outside_temp),
              states('input_number.threshold'),
              states('sensor.inside_temp')]|map('is_number')
          }}
        state: |
          {% set hysteresis=states('input_number.threshold')|float(0) %}
          {% set entity=states('sensor.outside_temp')|float(0) + states('input_number.offset')|float(0)  %}
          {% set limit=states('sensor.inside_temp')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
          {{ entity < limit }}

Great, thanks a lot. This is a selfmade hysteresis binary sensor, which is necessary because the built-in integration can not work with templates as limits.
I skipped the “availability”, don’t know what is good for.

This is my binary sensor in templates.yaml:

  - name: "Pool Wärmebedarf"
    unique_id: "poolneedheat"
    state: |
      {% set hysteresis=0.3 %}
      {% set actualtemp=states('sensor.poolactualtemp')|float(0) %}
      {% set settemp=states('input_number.poolsettemp')|float(0) + (hysteresis if this.state=='on' else -hysteresis)  %}
      {{ actualtemp < settemp }}

The only think, unique id is always ignored, it sets a unique id derivated from the name, in my case pool_warmebedarf…funny!

1 Like