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.
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!
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.
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) }}
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.
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.
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 }}
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.
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 }}
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. 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!
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.
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() 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):