Templated binary_sensor stability

I have a templated binary_sensor which decide if we are at night (or not). The template return is based on the sun elevation and the outside illuminance. The metered outside lux is not a stricly decreasing function for various reason. So, around the trigger value, the state of the binary_sensor may wobble.

My idea was to forbid the sensor to change its state for at least 4h. So I wrote something like this.

binary_sensor:
- platform: template
  sensors:
    night: 
      value_template: >-
        {% set last_state = states.binary_sensor.night %}
        {% if last_state is not none and now()-last_state.last_changed < timedelta(hours=4) %}
        {{ last_state.state }}
        {% elif states("sensor.balcony_illuminance") != "unknown" %}
        {{ state_attr("sun.sun", "elevation")|float < -2.0 and states("sensor.balcony_illuminance")|float <= 2 }}
        {% else %}
        {{ state_attr("sun.sun", "elevation")|float < -2.0 }}
        {% endif %}

This is triggering Template loop detected errors like this:

2020-10-25 20:00:48 WARNING (MainThread) [homeassistant.components.template.template_entity] Template loop detected while processing event: <Event state_changed[L]: entity_id=binary_sensor.night, old_state=<state binary_sensor.night=on; friendly_name=The night, icon=mdi:weather-night, device_class=light @ 2020-10-25T19:59:46.104737+01:00>, new_state=<state binary_sensor.night=off; friendly_name=The night, icon=mdi:weather-night, device_class=light @ 2020-10-25T20:00:48.083852+01:00>>, skipping template render for Template[{% set last_state = states.binary_sensor.night %} {% if last_state is not none and now()-last_state.last_changed < timedelta(hours=4) %} {{ last_state.state }} {% elif states("sensor.balcony_illuminance") != "unknown" %} {{ state_attr("sun.sun", "elevation")|float < -2.0 and states("sensor.balcony_illuminance")|float <= 2 }} {% else %} {{ state_attr("sun.sun", "elevation")|float < -2.0 }} {% endif %}]

I imagine this kind of unstable inputs and detecting the night is something very common for home automation. Do you have suggestion to improve a templated binary_sensor stability without triggering loop protection?

Have you looked into the delay_on: and delay_off: options for the template binary sensor?

https://www.home-assistant.io/integrations/binary_sensor.template/

I would also indent each of the {{ }} result lines.

But I think the problem is that you’re referencing that template sensor in its definition, and that’s causing HA to set up a listener for it that will trigger as soon as you return a value, which causes the template to be reevaluated. And so on, and so on.

1 Like

Yes, I’m using delay_on and delay_off, but I removed them for clarity, as they are not adressing the issue. They delay the beginning of the night, eventually removing wabble during the delay, but they are not ensuring the sensor will not change state again before the morning.

I understand your explication. Before doing this in the sensor itself, I was doing this in an automation and I didn’t run into this trouble. Don’t know why.
Here a snippet of the automation:

- id: '314159'
  alias: All Shutters Down when sunset
  description: ''
  trigger:
  - platform: state
    entity_id: binary_sensor.night
    from: 'off'
    to: 'on'
  condition:
  - condition: template
    value_template: '{{ (now()-state_attr("automation.shutters_down_sunset", "last_triggered")).seconds
      > 43200 }}'

If the value of sensor.balcony_illuminance occasionally reports off-nominal, transient values, I suggest you consider filtering it using the Filter integration.

I have a very similar challenge: to create a binary_sensor that reports when the interior light level is too dim (and serve as a trigger to turn on interior lighting). For the longest time, I have based this measurement on two light sensors:

  • Interior light sensor (located in a place that is not influenced by any nearby lights)
  • Exterior light sensor (located in a place that does not receive direct sunlight)

Though experimentation, I found suitable thresholds for each one. When both report lower they’re below their respective thresholds, it is considered to be “dark” inside the house.

Generally speaking, this approach works. However, the light sensors can occasionally reports an off-nominal transient that is either too high or too low. For example:

25, 24, 23, 27, 24, 22, 16, 21, 21, 20

If this spike/dip occurs near the threshold value, it can cause it to temporarily report it’s dark (or not dark) and then, moments later, report the opposite. I’ve used delay_on and delay_off but there have been situations where even that isn’t good enough.

Here’s a comparison of the unfiltered (red) to filtered values (pale blue) from both interior and exterior light sensors:

It’s clear that basing the binary_sensor on the filtered values will produce a better result.

I experimented with several filters (and combinations of filters) and ultimately decided the basic low_pass filter was adequate for my application. Both of the filtered light sensors are configured the same way:

#sensor:
- platform: filter
  name: "Patio Luminance Filtered"
  entity_id: sensor.patio_luminance
  filters:
  - filter: lowpass
    time_constant: 5
    precision: 1

The Template Binary Sensor is simply this (the thresholds were determined empirically based on my environment and are likely to be different for anyone else):

- platform: template
  sensors:
    is_dark:
      friendly_name: 'Is Dark'
      unique_id: template_is_dark
      value_template: >-
        {{ states('sensor.foyer_luminance_filtered') | int <= 20 and
            states('sensor.patio_luminance_filtered') | int <= 45 }}
      device_class: light