Incident Angle of Sunlight

I actually made this for someone else, thought I would share it here too. My covers are not automated.

To get it completely customized to your liking is quite tricky. As stated above it also depends on any buildings blocking the window, seasonality etc. Personally I would use a binary input to decide whether or not to lower the cover, then use my template to decide how far to lower them.

For the binary sensor you may use any number of sensor inputs, but I would try to keep it simple. If you use irradiance as input for your binary sensor your can set a threshold to decide whether or not there is enough light. But these measurements tend to not be very reliable due to grid based calculations and dynamic weather. Ofcourse you can also directly measure amount of ambient light via a lux sensor, but these will be affected by lowering the covers. Perhaps some kind of weather station on your roof to measure light would be best… or a light sensor in some window in the attic

1 Like

Ok this thread makes me feel stupid lol. Can somebody break this down for me. My wife would like me to close the blinds so her furniture does not get faded and this seems to be part of the answer.
So I goto the map calculator and I draw a line out of the “line of sight” out that window?
This then the Azimuth angle which my google foo states is an engineering term for direction facing?
Mine faces southwest.
In terms of elevation maybe b/c most house windows may be close to the ground that is zero but maybe if you were in an high story apartment building on mountain this would be more important?
I think I understand the part about the value reaching one when the sun is dead on facing the window. So perhaps an automation to close the blinds between a range would be the way to automate off this.
This thread makes me really understand my lack of understanding of celestial events but I am keenly aware of the fact that where I live the length of day and position of the sun in the sky changes alot depending on the time of year

Yep, azimuth is just the direction the window is facing. So north would be 0 (or 360) degrees, east 90 degrees, south 180 degrees and west 270 degrees. If your window faces exactly southwest then the azimuth would be 225 degrees (halfway between south and west is 45 degrees, so 225 total).

By elevation I just mean the vertical angle of the window, like on this picture (the person is the window):

Because the sun is so far away the actual height of the window is neglible even in an apartment building, the only thing that matters is the orientation of the window itself. The formula is such that if you have a completely vertical window the elevation should be 0 degrees. So if the sun is exactly on the horizon, the elevation of the sun will also be 0 degrees and so it will shine directly into your window.

I think the best way to figure out how to use it is to just play around with it a little bit. The sensor does not take into account hills or other object obstructing the sun light. You can use it as a threshold but you could also have some finer control by setting how far the sunscreen has to close based on the sensor’s value.

Hello! Thanks for your code, but I think something is not right, at least in my case. I just transformed the final value to a percentage, so instead of getting 0-1 I get 0-100%:
Replaced {{ ang_sim | round(3) }} for {{ (ang_sim * 100) | round(0) }}

and I get something like this, for these windows:

        {% set win_azi = 225 %}
        {% set win_ele = 0 %}


        {% set win_azi = 45 %}
        {% set win_ele = 0 %}


I am using always a value of elevation of 0, maybe I didn’t get the point of that and maybe that is the problem, because I get maximum value in “Traseira” at 5.00am, before sunrise. This does not seem to take in account that the sun is down or not, what am I doing wrong? Can you help, please? And thanks again for your code, I would really like to use it the right way.

Hello. I think your graphs make sense, the azimuth of your windows differ by exactly 180 degrees. Indeed this sensor does not check whether the sun is up or down, if you want that you can easily add it. The sensor just gives a value, what you want to use it for is up to you.

1 Like

I replaced the last line of your code and I’m going to try to see the results.
{{ ((ang_sim * 100) | round(0)) if (sun_ele > 0) else 0 }}
it really doesn’t matter the value, each implementation must analyse the threshold that is needed for each window, and probably cross this with weather conditions ou sun intensity (I use my solar power production sensor to estimate outside luminosity) so I can do something like:
{{ ((ang_sim * 100 * states('sensor.calculated_outdoor_illuminance')|int(default=0)/10000) | round(0)) if (sun_ele > 0) else 0 }}
This sure helps! Thanks again!

Thanks a lot for sharing, Stefan!
I used to have FHEM running for my home automation and I had made myself a quite similar script in perl. Now I was trying to migrate it to Home Assistant but all this Jinja code and YAML indententation rules drove me crazy… :woozy_face:

Your script made my day as it did the job right out of the box, nice work!
Let me just ask one question for clarification. Our house has these ‘Velux’-type skylights installed in a roof with an angle of 45 degrees - so in my case the definition would be {% set win_ele = 45 %}, correct? (At least that would be my guess if I got the vector math right…)

Hi, good to hear. Yes 45 degrees elevation would be correct :slight_smile:

Thanks for your reply. I have tested the sensor script for a few days now and I noticed that it returns a certain amount of angular similarity even when the sun is on the opposite side of the house. (see log graph below)

Even though this looks perfectly correct form a mathematical perspective (as far as I can tell), it was not quite what I needed for controlling the covers. So I tried some modifications and ended up using your cos_sim value combined with some extra conditions. As the dot product returns negative values if the vectors are pointing in opposite directions (sun illuminating the other side of the house), I had to ‘cut the curves’ for these undesired values and for negative sun elevations. To achieve this, I replaced the two last lines of your code by this:

{{ ((cos_sim * 100) | round(0)) if (sun_ele > 0 and cos_sim > 0 ) else 0 }}

This is what the resulting graph looks like:

I think the curves now represent the percentage of sun exposure of the several facades quite nicely. For example, you can see how the exposure of the front side (blue line) decreases steadily from sunrise until around 11:30 when the sun fully lights the left face (purple line). From that moment on, the front side is facing the sun no more and exposure of the aft face (cyan line) begins to rise.
The lines representing the roof areas (yellow/brownish and blueish/violet) seem to work as well. They also intersect at 11:30, but due to the 45 degree roof tilt, the sun begins to light the aft roof already at 10 o’clock and continues to light the forward roof until around 13:45. This agrees well with the observations I made when tracking the actual sun exposure.

I am looking forward to getting an outdoor brightness sensor (as proposed in this previous post), I will then further refine the logic by taking sensor values into account as well. Thanks to y’all for your ideas and inspiration!

1 Like

Hi, thanks for sharing. Indeed I did not take into account from which side of the window the sun is hitting. Glad to see you found a way that works. Are you already using it to control your covers? Mind sharing that too?

Ps. this post nicely explains why I chose to calculate the angular similarity rather than the cosine similarity (which you are using now, I guess it also works fine): vector spaces - Cosine similarity vs angular distance - Mathematics Stack Exchange

came here because of your recent post in Dutch Domotics Discord…
this is some impressive templating you are doing here, or, even more, some impressive math.

Do I understand this correctly, in that I can use this on any window, by only adjusting the orientation of that window (mine are all perpendicular :wink: so don’t need any other consideration) ?

Seems quite a complex system compared to a simple and (always spot on) light sensor in all honestly, but I do like the mathematical approach, just like we have that for virtual daylight

That is a nice reference, I learned a lot of new stuff about angular functions now…
It’s been quite a while since my uni math courses, but I think I mostly understood the point that is made. I mainly ended up with the cosine similarity because it felt a bit more intuitive for me. (I like how a value of 1 translates to full exposure and values of zero or below indicate no exposure.) I can not tell for sure if the discussed loss of precision at low angles and the more differentiated weighing is relevant in terms of solar radiation. Most probably you would also have to take into account how much of it is absorbed/reflected across different angles to make things more precise.

But now I’m getting close to nit-picking and such detailed considerations are (most likely) beyond the scope of most of us here I guess. I don’t want to turn this into a maths/physics thread, let’s keep these discussions in the respective forums… At the end of the day, we’re just trying to automate these bloody covers, right? :wink:

I have way too many other things going on at the moment, so unfortunately I cannot spend as much time on home automation as I’d like to… Nevertheless I spend more time on it than my wife would like me to… :rofl:

So there is nothing to share so far, but I will keep an eye on this thread and come back if I’ve had time to make up some automation ideas. Might take a while though… :man_shrugging:

Basically yes. As the calculations just rely on window orientation (in terms of azimuth and elevation/tilt) and sun position, there’s nothing more you have to care about in the first place. Just give it a try and then make your own observations if the calculations match the real situation.

Of course there’s potential for further refinement from that starting point, e.g. taking into account the shadowing effects of trees or other buildings around your house. But that would be a topic for a new thread then, I guess… :grin:

It’s very complicated, but it could be done using for example the open source database of Open Topography. I’ve seen it used in some PV installation tools to calculate the forecasted production for example.

Hi Stefan,

thanks a lot for posting this - I’ve been trying to reuse your template for my particular setup but I might need a bit of help to make it actually work correctly - let me try to explain:

I have a “bioclimatic pergola” with movable roof slats:

I’m trying to set up an automation that will keep them perfectly aligned with the angle of the sun (same as in the photo) to minimize the amount of shade they are casting. In the photo I’m facing almost directly north (27 NW), with the east to my right. In the morning when the sun if effectively shining perpendicular to the slats all I need is to set their angle to the same elevation as the sun and I’m good however at noon when the sun is directly behind me (at azimuth 207) I need them to be vertical to minimize the shade while I no longer care about the sun’s elevation.

I’ve been trying to come up with a formula combining the azimuth and elevation of the sun to come up with the slat angle but no luck so far although I’m pretty sure it’s pretty basic trigonometry. If I use your sensor set to azimuth 207 and 0 elevation, I never get incidence of 1 however I assume that’s due to your setup working in 3 dimensions to the plane of the window…

Is there a simple way to (re)use what you come up with for this application? I feel like it should actually be a simpler version of the formula, not a more complicated one…


Hello @Plawa,

Interesting you should ask this. I am currently working on a very similar problem that involves tilting a solar panel towards the sun to optimize production. In general, to minimize the angle of incidence (AOI) with the sun you require knowledge of both solar azimuth and elevation. Luckily for us, HA has this information in the sun.sun integration.

This paper explains the technical details really well: Rotation Angle for the Optimum Tracking of One-Axis Trackers

I will post the details of my project, including all the maths and templates pretty soon, hopefully today. You can then look, or I can help you, to adapt it to your project.

1 Like

Hi Stefan, thanks for the link to the paper, that was exactly the bit I needed, specifically this formula:

R = tan-1 [ tan θz sin (γs - γa ) ]

θz - Zenith (i.e. 90 - elevation)
γs - azimuth of the sun
γa - azimuth of the pergola

This is how I implemented it as a template sensor:

  - unique_id: pergola_slat_angle
    name: "Pergola Slat Angle"
    unit_of_measurement: "°"
    icon: mdi:angle-acute
    state: >
        {% set slat_azimuth = 207 %} 
        {% set min_slat_angle = 0 %}
        {% set max_slat_angle = 135 %}
        {% set deg2rad = pi/180 %}
        {% set sun_zenith = iif((state_attr('sun.sun', 'elevation') | float) < 0, 90, 90 - (state_attr('sun.sun', 'elevation') | float)) %}
        {% set ground_angle = (atan(tan(sun_zenith*deg2rad)*sin((sun_azimuth - slat_azimuth) * deg2rad))/deg2rad) | abs%}
        {% set over_axis = iif(sun_azimuth > slat_azimuth,-1,1) %}
        {% set slat_angle = (90 - ground_angle*over_axis) | round(0) %}
        {% set slat_angle = iif(slat_angle < min_slat_angle,min_slat_angle,slat_angle) %}
        {% set slat_angle = iif(slat_angle > max_slat_angle,max_slat_angle,slat_angle) %}

I first calculate the offset from vertical, then adjust it pos/neg based on the position of the sun relative to the axis of the pergola and then constrain it within the set min/max angle.

This angle is the used by a script that sets the pergola to that desired angle however it first needs to be translated into % for the tilt service:

service: cover.set_cover_tilt_position
  tilt_position: >-
    {% set min_angle = 0 %} 
    {% set max_angle = 135 %}
    {% set slat_percent = ((states('sensor.pergola_slat_angle') | int - min_angle)/(max_angle-min_angle)*100) |round(0)  %} 
  entity_id: cover.pergola_roof

It seems to work exactly as expected in all my simulated scenarios but I still need to run it through its paces on a sunny day…

Thanks for the nudge!

1 Like

Great to hear, looks very similar to how I implemented it for my project. Indeed the paper gives pretty much everything you need.

One tip, be careful with atan. Its range is limited between [-90, 90]. When you exceed it starts counting from 0 degrees again instead of 90. I suddenly remembered that when earlier today my solar panels started pointing away from the sun instead of tracking it…

As an alternative you could use atan2, which has a range of [-180, 180] :slight_smile:

ps, in your normalization formula under service: you are not subtracting min_angle from pergola_slat_angle. Doesn’t matter now because it’s 0 degrees anyway, but in case you might ever change it

Fixed the % calculation, thanks!

1 Like