Light automation based on single sensor

I would have hoped for this to be simple but it’s turning out to be a lot more involved and I am wondering if I am missing something basic - or if there is a more elegant way to do this.
The use case is simple enough.

I need to setup approx 25 lights to start switching on as it gets darker in steps using the input from a simple light sensor e.g.

  • if the sensor readings reach n1, switch on room 1 and room 2 lights at 50%
  • As it gets darker and sensor reaches n2 , switch on a few more
  • At n3, all indoor lights at full intensity
  • At n4 , add outdoor lights

The lights switch off at a pre-determined time

Ideally I would have liked the transition to be gradual (most of the lights support levels) but could not figure out a simple way to do so - hence created stepped scenes 1-6 with step 1 as all off and step 6 as all on

Initially I tried setting up simple templated based automation to trigger each scene based on the sensor hitting a value e.g. trigger scene 1 if sensor value > n1 and <n2 - this didn’t work as expected

Based on a similar thread, I created a set of template sensors to trigger scenes based on sensor state

    
      
- platform: template
  sensors:
    dark_1:
      entity_id:
        - sensor.ambient_light
      value_template: "{{ states('sensor.ambient_light') |  float > 18 }}"

- platform: template
  sensors:
    dark_2:
      entity_id:
        - sensor.ambient_light
      value_template: "{{ states('sensor.ambient_light') |  float > 21 }}"
      
- platform: template
  sensors:
    dark_3:
      entity_id:
        - sensor.ambient_light
      value_template: "{{ states('sensor.ambient_light') |  float > 23 }}"
      
- platform: template
  sensors:
    dark_4:
      entity_id:
        - sensor.ambient_light
      value_template: "{{ states('sensor.ambient_light') |  float > 26 }}"

and sample automation code for 1 step:

- id: '1594473621991'
  alias: Auto Lights - Step 2
  description: ''
  trigger:
  - entity_id: sensor.dark_2
    for: 00:01:00
    from: 'False'
    platform: state
    to: 'True'
  condition:
  - after: '11:00:00'
    before: '21:00:00'
    condition: time
  - condition: and
    conditions:
    - condition: state
      entity_id: sensor.dark_3
      state: 'False'
    - condition: and
      conditions:
      - condition: state
        entity_id: sensor.dark_4
        state: 'False'
  action:
  - scene: scene.lights_step_2

This is working a little better but still not reliable as the sensor (esphome) sometime resends its values which re-triggers multiple scenes .
Any thoughts or pointers?

There’s a few details that I’m not quite understanding, but as a strategy, how about something like:

- trigger:
  - platform: numeric_state
    entity_id: sensor.ambient_light
    above: 18
  - platform: numeric_state
    entity_id: sensor.ambient_light
    above: 21
  - platform: numeric_state
    entity_id: sensor.ambient_light
    above: 23
  - platform: numeric_state
    entity_id: sensor.ambient_light
    above: 26
  action:
  - service: scene.turn_on
    data_template:
      entity_id: >
        scene.lights_step_{{ {18: 1, 21: 2, 23: 3, 26: 4}[trigger.above] }}
      transition: 2.5
4 Likes

so are you saying

that if above: 23 was trigger it will scene.lights_step_3 turn on

that thinking out side the box

1 Like

That looks far more elegant than what I have . Although to be honest, the data template has me stumped .
Let me read up a bit on data templates again.

Also, apologies that I wasn’t very clear on the issue in my original post.
The intent is to have the automation trigger once when the conditions are met - and to stay that way unless the conditions change (e.g. once the sensor reaches say 21.1, trigger scene 2 and do not retrigger anything unless the value goes below 21 or above 23)
However, With a strategy based on the numeric_state, the automations tend to get re-triggered somewhat randomly even if the boundary conditions are unchanged

I believe you could add a switch or input number that holds the last activated scene.
For instance: you have a number that changes the same way as Phil templated to 1, 2, 3, 4, 5, 6 as the action of the automation.
This number is then used as a condition, the activating scene has to be > input number.
That way it will only trigger once and will count up to 6, then I guess you need to set it back to 1 in the automation that switches off the lights.

Regarding dimming of the lights, I think that is easier to do per light.
Trigger is light on
And the action is to fade in the light.

This is probably because clouds roll in and it gets darker, so scene 3 is activated, then the clouds go away and you get back to scene 2.

If you set up the ESPHome to not send every value and average out the values on a say 10 minute basis then I believe it will be better.

Umm, That is not the case.
Switching during day time is understandable.
However this also occurs after dusk and twilight end when the sensor value is well above the thresholds.

@pnbruckner’s automation will do half of that (very cleverly too, I might add, although I think it’s working “backwards” and turning more lights on the lighter it gets…) — it triggers whenever the ambient light sensor state increases across one of the defined thresholds. You’ll still need to write the “opposite” automation to turn things off when the light level drops.

There shouldn’t be anything “somewhat random” about it: if you’re finding problems with the short-term fluctuations in light level, you have some options:

  • average out the sensor readings in ESPHome as suggested by @Hellis81;
  • add a for:” declaration for each trigger (so only trigger if the light level goes above x and stays there for y seconds: I do this with my hall light with a 30s “on” pause and a 10m “off” pause);
  • build some hysteresis into the system by having different, lower levels for the switch-off automation — so you might switch on level 1 when the light goes below 18, and switch it off only when the light level rises above 20.

This is a good idea but I believe it’s better to do what I suggested and make sure it can only count up in scene number.
(That is at least how I interpreted the request)

Because it could drop to 16 and then go back up to 20 again. It’s completely possible but if you limit it to only be possible to count up then there is no risk of it happening

Nothing random about it.

An “above: X” trigger will not re-fire unless the value becomes X or less and then goes back above X again.

However the trigger can get re-initialized if the sensor’s state becomes something other than a numeric value (e.g., “unavailable”), or HA restarts, or the automation is turned off and back on, or automations are reloaded. Those are all known events that can cause the trigger to fire on the next sensor state change that meets the criteria. But they’re not random.

When a numeric_state trigger fires the trigger variable will contain the above value of the trigger that fired. The template uses that to look up the desired script number in a dictionary. I.e., this is a dictionary:

{18: 1, 21: 2, 23: 3, 26: 4}

and this expression extracts the value:

[trigger.above]

So if the trigger with the “above” value of 21 fired, then it will extract the value 2. That is then appended to the scene name.

Then you need to see what your sensor is actually doing and address that before you use it as a trigger to an automation. The automation for responding to light level changes is probably complicated enough; it shouldn’t have to do the job of filtering the sensor, too.

That was one of the things I didn’t understand, but assumed the OP knew how his own sensor worked.

That could be done by adding additional numeric triggers, but using below instead of above. Then the template could easily take that into account.

Agreed. There are also a few HA sensor platforms that could help filter/massage the light level sensor before being used in the automation.

Which is what I would have thought given code is meant to be definitive and certainly not random unless by design.
So your comment got me investigating and it turns out that my esphome devices are indeed going to unavailable for very short intervals - wasn’t affecting anything else as they reconnect pretty quickly but certainly an issue - especially for this.

In fact there is a long running thread regarding esphome and sensors/ devices periodically going unavailable.
Let me try switching the sensor in question over to Tasmota MQTT and see if I see this recurring.

And Thanks for that explanation - It is indeed quite clever and elegant.
As pointed out by @Troon , i have added a for: declaration (1 min) for now .
It’s just about getting dark here so let me see how it works in practice.

Next step would be to stabilize the sensor (hopefully Tasmota will do the trick) and then move the automation to the template you have suggested

Unavailable usually means the WiFi is not connected, so I doubt tasmota will solve it.

There may be other & better ways, but if you just want to filter out unavailable states, then this is a common technique:

sensor:
  - platform: template
    sensors:
      ambient_light_filtered:
        entity_id: sensor.ambient_light
        value_template: >
          {% set cur_val = states('sensor.ambient_light_filtered') %}
          {% set new_val = states('sensor.ambient_light') %}
          {{ new_val if new_val != 'unavailable' else cur_val }}

Then use sensor.ambient_light_filtered in the automation.

3 Likes

Probably a digression - I am not sure if it’s an esphome issue or an issue with the esp8266s.
Either way, tasmota ran stable for me for years for 15 odd esp8266s.
I moved to esphome last week and the unavailable messages started happening right after.
Now it’s entirely possible that even Tasmota has brief disconnects but the regular mqtt stack may probably not even realize that the device became unavailable.

I have moved the sensor in question to tasmota now and have switched to @pnbruckner ‘s code
Will report back on how it goes :slight_smile:
If I see issues, will surely try out the filtered value template (which I am anyway bookmarking for future use cases)

You , Sir are a value template wizard!

2 Likes

Almost there but there seems to be something wrong with my understanding of how triggers work.

  1. The sensor has been moved to tasmota and is now stable- it reports values from 0 to 330 with increasing values as it gets darker (i.e. 330 is complete darkness)

  2. The code in use - snippet 1 is supposed to switch on lights as it gets darker while snippet 2 is the reverse

  3. Scenes are set as scene.lights_step_n with n from 0 to 6 - with 0 as all off (i.e. bright outside) and progressively to 6 as all on at max (i.e. dark outside)

Snippet 1:

- alias: Automated lights On
  trigger:
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 160
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 180
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 200
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 220
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 240
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    above: 260
  condition:
  - after: '11:00:00'
    before: '21:00:00'
    condition: time
  
  action:
  - service: scene.turn_on
    data_template:
      entity_id: >
        scene.lights_step_{{ {160: 1, 180: 2, 200: 3, 220: 4, 240: 5, 260: 6}[trigger.above] }}
      transition: 15

Snippet 2:

- alias: Automated lights Off
  trigger:
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 150
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 170
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 190
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 210
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 230
  - platform: numeric_state
    entity_id: sensor.terrace_analog_a0
    below: 250
  condition:
  - after: '11:00:00'
    before: '21:00:00'
    condition: time
  
  action:
  - service: scene.turn_on
    data_template:
      entity_id: >
        scene.lights_step_{{ {150: 0, 170: 1, 190: 2, 210: 3, 230: 4, 250: 5}[trigger.below] }}
      transition: 15

The issue is that snippet 2 is acting reverse of what’s expected - The current sensor reading is 90 so I would expect snippet 2 to activate scene_lights_step_0 but it seems to be activating scene_lights_step_5 instead

I think this is due to how numeric_state triggers work, which I explained above. The first sensor state change can cause more than one trigger to fire, and in your example, 90 would cause all the triggers to fire.

As a workaround, I’d suggest reversing the order of the triggers in the second automation. Triggers are evaluated in order, so that would then make the lowest below value evaluated last, and it should be the last to “take effect.”

If that doesn’t work then we can probably come up with something better.

Reversed the trigger order and it all seems to be in order now.
Saying Seems to be because I the logbook or history isn’t showing the exact scene triggered so I guess I will have to observe it for a day or two.

On a related note, is there a way to evaluate a two-step template (like yours) in the template editor?
Would be very useful for debugging .