UPDATED March 2018:
- can now adapt to any time zone (only relies on azimuth and sun-related data, such as sunrise/sunset, next_rising, next_setting, next_dawn, next_dusk) and becomes more a “circadian” light project than it was before
- takes into account differences in the actual length of days in hours, depending on seasons (winter to summer), so the light experience automatically “evolves” over the calendar year
- Light experience that differentiates between daytime, dusk, dawn and night periods
Hi there!
I’ve been fiddling with HA since July 2017 and this is my first post. You’re all a great community and (besides my wife) you inspired me to publish something which I believe is really cool and might help others as well.
Since I am a light addict (there are scientists who confirm the positive effects of natural light on our well-being) I started playing around with my Philips Hue, in order to find a way of automating the light temperature (in kelvin or mired) during the day.
This automation simulates the sun color temperature from sunrise to sunset, from a warm light in the morning over a bright light at noon with more blue color elements, and back to a warm (almost yellow) light in the evening.
It also takes into account that summer brings longer days than winter, thus the color temperature reaches its peak faster in winter than it does during summer.
Time | Color Temperature in Kelvin | Color Temperature in Mired |
---|---|---|
High Noon Sun | 5300-6500 kelvin | 188-154 |
Sunrise/Sunset | 2000-3000 kelvin | 500-333 |
As Philips Hue can “digest” mired values directly through the color_temp
attribute, I came up with the idea to create a background automation which seamlessly updates the mired value over the day. It can then update the color_temp
on any trigger, in this example I chose a time-based check (every 15 minutes).
Here’s how I did it:
First, I created a sensor that dynamically calculates the approximative length of the day. The product is something I call the “factor u” which I need in a table calculation.
- platform: template
sensors:
length_of_day_factor:
friendly_name: 'length of day factor for circadian light calculation'
value_template: >
{% if is_state('sun.sun' , 'above_horizon') %}
{% set daylength = ((as_timestamp(states.sun.sun.attributes.next_setting) - as_timestamp(states.sun.sun.attributes.next_rising)) / 3600) + 24 %}
{{ (daylength*-0.0063616)+0.11131 }}
{% endif %}
Please note that the daylength variable uses a trick when it comes to calculating the actual length of a day. Since I found no “ready-to-consume” value in the sun
component, I simply took the values of the next day and substracted it by 24 (hours).
The length_of_day_factor actually takes into account values which I have manually and previously calculated in a table calculation sheet (unfortunately I cannot upload this sheet to the post). In summary, it translates the actual length of a day into a value which influences the speed a mired value changes over time.
Second, I built a sensor template which integrates the automatic calculation of mired over day. It looks like this:
- platform: template
sensors:
farbtemp:
friendly_name: 'circadian light - color temperature'
unit_of_measurement: 'mired'
value_template: >
{% if is_state('sun.sun' , 'above_horizon') %}
{{ ((states.sensor.length_of_day_factor.state | round (5))*((states.sun.sun.attributes.azimuth)-180)**2 + 175) | int }}
{% elif (as_timestamp(states.sun.sun.attributes.next_dusk)) - (as_timestamp(states.sun.sun.attributes.next_setting)) < 0 or (as_timestamp(states.sun.sun.attributes.next_rising)) - (as_timestamp(states.sun.sun.attributes.next_dawn)) < 0 %}
350
{% else %}
390
{% endif %}
Some remarks: this template runs in the background and calculates the appropriate mired (microreciprocal degree) value according to the progression of the sun (azimuth). The updated version does no longer only rely on the azimuth which I found being too volatile: In Zurich/CH it ranges from (sunrise) 53 to 306 (sunset) of azimuth in summer and 125 to 235 azimuth in winter. Given that I have adapted the calculation to my geographical location, the “circadian” mired value kicks in at the right point of time (from sunrise until sunset) and is independent from changes in “where does the sun go up and down” (azimuth). There also is no impact by daylight saving time, different time zones, differences in sun elevation (summer = sun climbs higher, winter = sun climbs lower), etc. The “real value” calculation only kicks in while the sun is above the horizon. Outside of this period it either falls back to 390 mireds (which is a cozy yellowish light) in the evening and night, or a lightbulb standard value (350 mired) during dusk/dawn periods.
You can find the kelvin-to-mired-conversion here: https://en.wikipedia.org/wiki/Mired
The algorithm can be replicated in a table calculation through curve sketching and it creates a graph that looks like this:
Second, I integrate the states.sensor.colortemp.state
value into any light automation, e.g.:
- alias: 'office - update mireds every 15 minutes'
trigger:
platform: time
minutes: '/15'
seconds: 00
condition:
- condition: state
entity_id: light.office
state: 'on'
action:
- service: light.turn_on
data_template:
entity_id: light.office
color_temp: "{{ states.sensor.colortemp.state }}"
Any comments or questions are welcome! Enjoy!