Outdoor illuminance template sensor

You are welcome. Remember this is a work in progress. this morning is the first time I am getting to watch the chart climb. So I’ll see soon if I understand the code as well as I hope I do. :blush:

It has been overcast this morning, until about 10 minutes ago. Hopefull that chart will rise quickly once Accuweather realizes the sun is out.

My gut is telling me the values are off by a factor of 10. It’s 10 am and my lx value is still only 622!

As an experiment I am changing the sun_factor calculation to multiple the time of day factors by 10. I will keep you up to date as the day progresses.

If you have any insight, please share your thoughts on these calculations.

{% if sunrise_end < right_now and right_now < sunset_begin %}
  1
{% elif sunset_end < right_now or right_now < sunrise_begin %}
  0
{%- elif right_now <= sunrise_end -%}
  {{ ((right_now - sunrise_begin) / (60*60*60)) * 10 }}
{%- else -%}
  {{ ((sunset_end - right_now) / (60*60*60)) * 10 }}
{% endif %}

Watching progress with great interest. Keep up the good work

p.s. loving the friendly name

1 Like

Screenshot 2020-09-21 113605
The numbers are looking much more like I expected. I updated the code in the original code in the first post to multiple the sun_factor by 10.

1 Like

Hmm…Looking at the Phil’s original component his lx appears to have a hard limit of 10,000. At 12:40pm mine is 10,886.

I wonder if the calculations are off, or do I just need to limit the sun_factor to never go above 1?

Update: 1:30pm - I made the change to the code.

Continued from @Whiskey’s reply on Outdoor illuminance estimated from weather conditions - #116 by Whiskey

I have learned to do so much with templates over the last 2 years with Home Assistant! But, I am really happy with the automation/script choose and variable methods added to recent Home Assistant updates.

Please do use this. I am sharing this code in the hopes that someone like you will find it useful. I do think this has the potential to be a replacement for Phil’s terrific component – as long as the numbers don’t go so crazy that lights turn on in the middle of the night – with the advantage that guys like you and me can modify it to work with whatever components we want! :slight_smile:

1 Like

The latest updates to automations is awesome. The rate of development is fire right now.

I never knew about jinja macros. Can’t wait to rewrite some stuff a lot neater by implementing them.

I’ve just bought a BH1750FVI Lux sensor to measure actual lux levels in my apartment. Going to use it to calibrate my rest API cloudiness based Illuminance guesser sensor. I’m a little scared the hardware based lux sensor will grow from a calibration tool and become a permanent part of my system, making all this template guesstimating irrelevant!

Do you think my ‘current cloud cover percentage polled every 15 minutes’ idea will return more accurate and smoother results rather than five step values from icon/symbol data? Probably won’t notice much difference in the long run because the lights are working great as is.

If I can use your work in converting @pnbruckner’s python code to jinja then I can see me rolling all the templates into one sensor that spits out a brightness and temperature ready to be consumed by automations directly.

I will learn python one day!

All very cool.

Yeah, they referred to as functions in most other languages. The output of the macro is whatever you output (using {{ }}). You can also pass values into the macro. For example this macro receives a comma separated list and returns a sorted list ready for Alexa to speak.

{%- macro get_friendly_list(list) -%}
  {%- set comma = joiner(', ') -%}
  {%- for item in list|sort if list|length > 0 -%}
    {{ ' and ' if loop.last and not loop.first else comma() -}}
    {{ item|title }}
  {%- endfor -%}
{%- endmacro -%}

{%- set names = ["John", "Paul", "Ringo", "George"] %}
{{ get_friendly_list(names) }}
1 Like

Way to… lite a fire under me. :wink:

Looking at the history, the morning and daytime are working as I believe they should. However, the evening drops off an 10000 lx edge straight to zero. This obviously isn’t the desired outcome. So, I know there must be a logic problem if the sun_factor if statement.

Screenshot 2020-09-22 092333

P.S. I cheated and used a database editor to weed out all of the erroneous values I created with mistakes throughout the day. That’s why that chart looks cleaner than it should. :blush:

FWIW, macros are advantageous because they reduce code-length when they are called more than once. They are only called once in your Template Sensor so that advantage is lost. In fact, they serve to increase code-length (due to the macro’s delimiting statements and the macro call itself).

Here’s a shorter version of your code (without macros). Please note, it is untested because I don’t use the sun2 custom component.

platform: template
sensors:
  illuminance:
    friendly_name: Outdoor Illuminance Educated Guessor
    icon_template: mdi:brightness-auto
    unit_of_measurement: lx
    value_template: >
      {% set condition_factors = {
          "10000": ("clear", "clearnight", "sunny", "windy", "exceptional"),
          "7500": ("partlycloudy", "partlysunny", "mostlysunny", "mostlyclear", "hazy", "hazysunshine", "intermittentclouds"),
          "2500": ("cloudy", "mostlycloudy"),
          "1000": ("fog", "rainy", "showers", "snowy", "snowyheavy", "snowyrainy", "flurries", "chanceflurries", "chancerain", "chancesleet", "drearyovercast", "sleet"),
          "200": ("hail", "lightning", "tstorms") } %}
      {% set current_condition = states("sensor.openweathermap_condition") %}
      {% set ns = namespace(condition_factor='') %}
      {%- for factor in condition_factors if current_condition in condition_factors[factor] -%}
          {% set ns.condition_factor = factor }}
      {%- endfor %}

      {%- set right_now = states.sensor.time.last_updated.timestamp() %}
      {%- set sunrise = as_timestamp(states("sensor.sunrise")) %}
      {%- set sunrise_begin = as_timestamp(states("sensor.dawn")) %}
      {%- set sunrise_end =  sunrise + (40 * 60) %}
      {%- set sunset = as_timestamp(states("sensor.sunset")) %}
      {%- set sunset_begin = sunset - (40 * 60) %}
      {%- set sunset_end = as_timestamp(states("sensor.dusk")) %}

      {% if sunrise_end < right_now < sunset_begin %}
        {%- set sun_factor = 1 %}
      {% elif sunset_end < right_now or right_now < sunrise_begin %}
        {%- set sun_factor = 0 %}
      {%- elif right_now <= sunrise_end -%}
        {%- set sun_factor = ((right_now - sunrise_begin) / (60*60*60)) * 10 %}
      {%- else -%}
        {%- set sun_factor = ((sunset_end - right_now) / (60*60*60)) * 10 %}
      {% endif %}
      {%- set sun_factor = sun_factor if sun_factor|float < 1.0 else 1 %}
    
      {{ ns.condition_factor }} {{ (sun_factor|float * ns.condition_factor|float) | round }}
1 Like

I found a mistake that make this more accurate. I accidentally assigned the dusk sensor to sunrise_end instead of sunset_end. I am updating the code in the first post.

{%- set sunrise = as_timestamp(states("sensor.sunrise")) %}
{%- set sunrise_begin = as_timestamp(states("sensor.dawn")) %}
{%- set sunrise_end =  sunrise + (40 * 60) %}
{%- set sunset = as_timestamp(states("sensor.sunset")) %}
{%- set sunset_begin = sunset - (40 * 60) %}
{%- set sunset_end = as_timestamp(states("sensor.dusk")) %}

I agree, however I often use functions to break up the functionality of a complex bit of code into manageable chunks. That works best for my brain while I am figuring out a new process.

I’ve learned a lot from your code optimization replies over the years! Thank you! I am unsure of the use of namespace though. I will have to look into that.

FWIW, because your template uses the current time to perform a calculation, you can dispense with this line:

{% set FORCE_UPDATE_WORKAROUND = states("sensor.time") %}

and incorporate sensor.time directly in the time calculation. Simply replace this line:

{%- set right_now = as_timestamp(now()) %}

with this:

{%- set right_now = states.sensor.time.last_updated.timestamp() %}

The subtle difference between the two is that the resolution of the first one is in seconds whereas the second one is in minutes. However, for the purposes of this template, that’s sufficient.

2 Likes

Sure but there are only two so-called ‘chunks’ here. Effectively you’ve done this (pseudo-code):

macro add()
  return 2+2
macro subtract()
  return 2-2

x = add()
y = subtract()

{{ x }}
{{ y }}

I can see how that’s advantageous if there’s a need to call add or subtract several times but, in this case, there isn’t. Without macros it’s just this (with no loss of legibility):

x = 2+2
y = 2-2

{{ x }}
{{ y }}

A variable used within a for-loop has a limited scope. Changes to its value inside the loop aren’t accessible outside the loop.

You can easily demonstrate it for yourself. Notice the final value of x inside the loop is 4 but outside the loop it’s 0.

{% set x = 0 %}
{% for i in range(1,5) %}
  {% set x = i %}
  i={{ i }}, x={{x}}
{% endfor %}

The value of x is: {{ x }}

namespace allows us to define variables with a broader scope.

{% set ns = namespace(x = 0) %}
{% for i in range(1,5) %}
  {% set ns.x = i %}
  i={{ i }}, x={{ns.x}}
{% endfor %}

The value of x is: {{ ns.x }}

In this version, the value of ns.x outside the loop is the same as inside the loop.

2 Likes

Thank you for that! I’ve felt like I saw there was a more elegant way to do that… I just wasn’t finding it.

I also see that namespaces are necessary to get a value out, for some reason, when you stick and if statement on the same line as your for statement. :man_shrugging: But, I’ve got my head around them now… and this still adds clarity while not really making my variable names longer.

{% set factors = namespace(condition='',sun='') %}
...
{%- for factor in condition_factors if current_condition in condition_factors[factor] %}
  {%- set factors.condition = factor %}
{%- endfor %}
...
{% if sunrise_end < right_now and right_now < sunset_begin %}
  {%- set factors.sun = 1 %}
...
{{ (factors.sun|float * factors.condition|float) | round }}

I suspected something along those lines… OOOOOOhhhh, I didn’t have that problem before because I was returning that value from a macro directly! So, I wasn’t having that problem.

I wonder if I have unconsciously resorted to macros more often than seems necessary without realizing this! :grin:

Oh and as far as that goes. I agree… and I sometimes, not always optimize my code in the end. In this case I was trying to convert Python code doing some math I didn’t understand at first.

Thank you.

1 Like

There’s probably no pressing need to make sun a namespace variable (because its value isn’t calculated within any for-loop) but you can certainly do that for, I guess, the sake of neatness.

Just keep in mind that they are NOT precisely the same thing. Paste this into the Template Editor and you’ll see that the one based on now() updates every second whereas the other one, based on sensor.time is every minute.

{{ now().timestamp() }}
{{ states.sensor.time.last_updated.timestamp() }}

{{ now().timestamp() | timestamp_local() }}
{{ states.sensor.time.last_updated.timestamp() | timestamp_local() }}

For many time calculations, every minute is ‘good enough’. For calculations that are comparing times down to the second, that’s not ‘good enough’.

1 Like

I knew it was technically unnecessary. But there is enough chaos going on in my brain already. It helps me to find ways to keep code neat.

That’s actually better. I don’t want this to update too often.

now() is a function, not an entity, so Home Assistant doesn’t assign a listener to it. Therefore there was never any chance of the Template Sensor being evaluated every second.

You can see that in the Template Editor which now (in 0.115) reports which entities are being monitored (if any):

2 Likes