Help with automation using a light sensor to adjust the brightness of a bulb

I have an ESP8266 sensor (LDR) and I wrote this automation to turn the light brightness up when it gets dark.

alias: 'Light Level Office Brightness Increase '
description: ''
trigger:
  - platform: state
    entity_id: sensor.officelightlevel
    for:
      hours: 0
      minutes: 0
      seconds: 1
condition:
  - condition: numeric_state
    entity_id: sensor.officelightlevel
    below: input_number.office_light_target
action:
  - device_id: 3d85ad48c7142875bb4b320cdd1ef8b9
    domain: light
    entity_id: light.office_bulb
    type: brightness_increase
mode: single

I have the same automation that turns it down if its above the target however my 2 automations cannot achieve the exact light level so they end up fighting each other putting the brightness up and then down.

Any idea how I could stop that happening as it works well initially but once near to the target (say 100) and it reaches 100.1 dropping the brightness down by 1 step drops it to say 97 and then its incremented back up again.

2 Likes

If your other automation uses the same trigger then I can see why it might be causing an issue.

you aren’t using a designated state for the sensor to be in so every change of the sensor (or its attributes) will cause the automations to trigger. For example, it will trigger on every step 0->1->2->3->2->3->4… or from 100->99->98->99… or if any of the attributes (last_updated, last_changed,…) change.

then depending on the state of the light it could get into the weird state that you are seeing.

Ignore the trigger, I am aware of the trigger just I had some issues as the trigger time pattern didn’t work properly I will fix this later on.

My issue is around the condition comparing
sensor.officelightlevel with a helper input_number.office_light_target
My issue is that both values will never match because hive bulb is “stepped” and the lux reading from the sensor is very accurate.
There would be no way to get say the target to 100 and have the bulb match it

What happens is the automation fires and pulls the brightness of the bulb up but when it goes slighly over the target then the other automation fires bringing it down below the target then the first automation starts firing again pulling it up again.

Yes, that’s because you have the trigger mis-configured and/or you have no hysteresis built into the system so your automations are always “hunting” for the correct value.

let’s say you want to keep the brightness at ~800 lumens.

if the lux sensor is reading 775 lumens then you can repeat the “increase brightness” action until the brightness = 800 lumens. And the have the second automation lower the brightness if it gets to be above 825 & repeat the decrease brightness action until it’s less than 800.

Or set the setpoint to have a +/- setting of whatever the sensitivity of the sensor is (795 - 805, etc)

That way the automations won’t be fighting each other trying to control the brightness to tightly.

I am trying to achieve exactly what you wrote, but I cannot figure out how to do a mathematical operation as part of the automation. I will try and find out.

How much does the “brightness_increase” action raise the brightness per call?

you can just do a “repeat…until” and use the brightness >= setpoint as the “until” part. no math involved.

Hi @daknightuk,

You need a target range instead of a fixed target level.

So, lets say your original target is 100 Lx.

Disable the automation that reduces the light and run the automation that increases the light several times and note the level it really reaches after 100 Lx. Lets say the worst case scenario it hits 120 Lx. (+20 compared to target)

In the same way figure out the lowest level it reached under 100 Lx by your automation that reduces the light. Lets say it is 75 Lx. (- 25 compared to target)

Then your target range should Max( 20; 25 ) = 25 wide.
20/2 = 10 below and
25-10 = 15 above your original target.
That is 90 to 115.

So, set your target (or comparison level) to 90 Lx in the automation that increases the light and when it runs it might hit 90+20 = 110 Lx.
And, set your target to 115 Lx in the automation that decreases the light. And when it runs it might hit 115-25 = 90 Lx.

This should do the job and keep them apart.

If the automations still fight Increasing/decreasing the light, you can experiment widening your range in steps (example 85-120) until they get enough apart that they don’t cross each other level conditions

If you need help calculating the target range, please give me the figures (Original target, lowest, highest levels reached) and I can do the calculation for you.

Please let me know if this doesn’t make sense. Other wise let me know if it works for you.

Kind regards,
Ghassan

:man_shrugging:

isn’t that what I already said three hours ago?

Right, just explained in details with examples :slight_smile:

So I’m new to maths calculations in HA, I think I may have done my first template??
Finding any information on templating is hard but this is inside my config.yaml file now

    sensors:
      utll:
        friendly_name: "UpperTarget Light Level"
        unit_of_measurement: "Lux"
        value_template: '{{ states("input_number.office_light_target") |float + 20 }}'
      ltll:
        friendly_name: "LowerTarget Light Level"
        unit_of_measurement: "Lux"
        value_template: '{{ states("input_number.office_light_target") |float - 20 }}'

I haven’t rewritten the automations but I’m going to take some time to look at it better tomorrow

So I worked out that my bulbs have a brightness attribute
which seems to go up by 63.75 each time so guessing I can look to map the lux to a specific value.
However when i go into developer tools and try and set the attribute through the Call Service nothing happens

- service: light.turn_on
    data:
      entity_id: light.office_bulb
      brightness: 255

Hi @daknightuk,

The indention is wrong. Should be like this:

- service: light.turn_on
  data:
    entity_id: light.office_bulb
    brightness: 255

Probably you should use brightness_step_pct instead, so you can increase decrease by x % a time.

For more detail on light.turn_on see : Light - Home Assistant (home-assistant.io)

I you like, share your automation code here, then I will take a look at it.

Kind regards,
Ghassan

My code to turn up the brightness ended up being this:

alias: Turn up the brightness on the office bulb
description: ''
trigger:
  - platform: time_pattern
    seconds: '15'
condition: []
action:
  - repeat:
      while:
        - condition: numeric_state
          entity_id: sensor.officelightlevel
          below: input_number.office_light_target
      sequence:
        - service: light.turn_on
          data:
            brightness_step_pct: 10
          target:
            entity_id: light.office_bulb
mode: single

input_number.office_light_target is a helper (slider) from 0 to 100
sensor.officelightlevel is a light dependant resistor connected to an ESP8266 module,
Worked out when the bulb is on 100% brightness (at night) the lux reading is 101 so ideal!

Now I need to write the code which reduces the bulb brightness when the lux reading is over the target i.e the sun is shining into the room.

Then I will link this to an “occupancy” script I wrote for my office so that all of this will only happen provided i’m in my home office.

Code for dimming the bulb back down again:


alias: Turn down the brightness on the office bulb
description: ''
trigger:
  - platform: time_pattern
    seconds: '15'
condition: []
action:
  - repeat:
      while:
        - condition: numeric_state
          entity_id: sensor.officelightlevel
          above: sensor.utll
      sequence:
        - service: light.turn_on
          data:
            brightness_step_pct: -10
          target:
            entity_id: light.office_bulb
mode: single

Addition to Configuration.yaml which adds a sensor called utll (upper target light level) as the lux doesn’t correspond with the bulb and this stops both scripts fighting each other.


sensor:
     #template for upper target light level value
  - platform: template
    sensors:
      utll:
        friendly_name: "UpperTarget Light Level"
        unit_of_measurement: "Lux"
        value_template: '{{ states("input_number.office_light_target") |float + 20 }}'

My only remaining question is… is this configuation.yaml the best way to calculate the value for use on the automation or is there a better way to do this?

Hi @daknightuk,

Looks good, I will though suggest you only have one automation handling both situations. You can do that using the “choose” option, to handle different situations in different conditions.

Be aware that seconds: ‘15’ for the time pattern means each time the seconds is 15, this is once per minute. But I guess you want it to be every 15 seconds then use - seconds: '/15’

The update interval may not be shorter than the interval needed for your light sensor to discover and update changes, otherwise you end up in changes up and down passing your target before your sensor discovers that, then move the other way and so on, and you’ll never rest at your desired target.

And as you do the check every 15 seconds, no need to loop, as the light doesn’t change much during that period. So just adjust 10% up or down, wait 15 Seconds and see if further action is needed.

I made some test and recognized that moving by brightness percentage is not a good idea, as the steps are small at low levels and large at high levels. This gives a less consistent adjustment and your range might need to be adjusted according to your target. So I prefer using absolute steps instead

Furthermore you need not defining utll, you can by the use of templates make comparison to your target + /- 10 which I suggest to have equal intervals on both sides of you target instead of you only add 20 to the upper level.

One more thing, I recommend that you gather your parameters in the variables section, so it is easy to adjust these one central place.

Finally, when you want to add Occupancy, you can do that in the condition of the automation. We can get back to that later.

So here is my suggestion for the automation:

alias: Adjust brightness on the office bulb
description: ''
trigger:
  - platform: time_pattern
    seconds: '/15'
condition: []
action:
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ states('sensor.officelightlevel')|float <
              states('input_number.office_light_target')|float - range/2 }}
        sequence:
          - service: light.turn_on
            target:
              entity_id: light.office_bulb
            data:
              brightness_step: '{{ my_brightness_step }}'
      - conditions:
          - condition: template
            value_template: >-
              {{ states('sensor.officelightlevel')|float >
              states('input_number.office_light_target')|float + range/2 }}
        sequence:
          - service: light.turn_on
            target:
              entity_id: light.office_bulb
            data:
              brightness_step: '{{ -1 * my_brightness_step }}'
    default: []
mode: single
variables:
  range: 20
  my_brightness_step: 10

Please try it and let me know if it works, or still gives you any challenges.

Let me also know if you have any questions trying to understand the code which is crucial
for being able to maintain it afterwards :slight_smile:

1 Like

@ghassan I just want to thank you for introducing me to the idea of “choice” I totally didn’t understand it and now I think I do. I haven’t tried your script yet, I was busy writing an automation which turns an LED on when a relay is switched on and I managed to get the Choice functiion to work on this.
For ages I’ve been writing ON scripts and OFF scripts seperately now it seems I can halve my scripts with a bit of work so thanks for that
I’ve “read” your script and I totally understand how it works, I am surprised that variable declaration is that the end I always learnt to declare variables at the beginning when I used to learn programming.

Final script that works great! with a couple of numeric tweaks.

Helper Occupancy timer runs when motion is happening in the office.

alias: Adjust brightness on the office bulb (faster)
description: ''
trigger:
  - platform: time_pattern
    seconds: /15
condition:
  - condition: state
    entity_id: timer.office_occupancy
    state: active
    for:
      hours: 0
      minutes: 0
      seconds: 1
action:
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ states('sensor.officelightlevel')|float <
              states('input_number.office_light_target')|float - range/2 }}
        sequence:
          - service: light.turn_on
            target:
              entity_id: light.office_bulb
            data:
              brightness_step: '{{ my_brightness_step }}'
      - conditions:
          - condition: template
            value_template: >-
              {{ states('sensor.officelightlevel')|float >
              states('input_number.office_light_target')|float + range/2 }}
        sequence:
          - service: light.turn_on
            target:
              entity_id: light.office_bulb
            data:
              brightness_step: '{{ -1 * my_brightness_step }}'
    default: []
mode: single
variables:
  range: 5
  my_brightness_step: 18
1 Like

Hi @daknightuk ,
I just wanted to tell you that I took the automation a step further and made two kind of optimizations:

1: Made the code more compact, no need to use the choice any more
2. Using the mathematical binary search method, I made the step size dependent on distance to target ( Distance/2), this makes it:

  • Quicker get close to the target, by larger steps, when far away.
  • And when it is close, the steps get small and the risk to move past the target are reduced.
  • The margin (previously called range) can then be narrower.

It seams to work as expected.

Here is my current version ( Modified to your entities)

alias: Adjust brightness on the office bulb 2
description: ''
trigger:
  - platform: time_pattern
    seconds: /15
condition:
  - condition: state
    entity_id: timer.office_occupancy
    state: active
    for:
      hours: 0
      minutes: 0
      seconds: 1
action:
  - service: light.turn_on
    target:
      entity_id: light.office_bulb
    data:
      brightness_step: |-
        {% if ( brightness_step|abs > (brightness_margin/2)) %}
          {{ brightness_step }}
        {% else %}
          0
        {% endif %}
mode: single
variables:
  illuminance_target: '{{states(''input_number.office_light_target'') }}'
  illuminance_margin: 5
  max_illuminance: 580
  max_brightness: 255
  brightness_margin: '{{ ( illuminance_margin * max_brightness / max_illuminance )|int }}'
  distance_to_target: >-
    {{ illuminance_target -
    states('sensor.officelightlevel')|float }}
  illuminance_step: '{{ distance_to_target / 2  }}'
  brightness_step: '{{ ( illuminance_step * max_brightness / max_illuminance )|int }}'

You need to:

  • Measure maximum illuminance in your office and adjust the value of the max_illuminance variable to that.
  • Experiment with different values of the illuminance_margin. In this implementation it is the amount on each side of the target and not as the previous range which was the double of that.

Hope you would like to try it, and I look forward to get your evaluation :slight_smile:

4 Likes

I really like it, as you said yourself I need to understand it to maintain it so I will take some time to work out how it works.
The script works well even without me adjusting the max_illuminance figure which in my case is 101.

David

Hi @daknightuk

Great to hear that, :+1:

But notice that max_illuminance should be the illuminance value measured by your sensor when the light(s) are on max brightness (during the evening, where the sun don’t contribute to the illuminance level).

Are you sure it is only 101? And is your target around 100? could these be that close?

Kind regards,
Ghassan

@ghassan yes when there is no light contribution from outdoors the top reading from my bulb is around 101 - 104 lux. The sensor faces from the wall towards the window so that the light level adjusts with the sunlight.
This was my reason for doing it as I was working from home and finding that the light was on with the sun blazing through the window.