DIY Circadian Lighting. no flux and no custom components

This is actually why I went with the

I started with the list that you had perscribed but I found that I wanted an RGB value for my nightlights instead of a color temperature. I like the redder hue rather than the max 500 Kelvin.
With this, I had to created two automations: automation.circadian_day and automation.circadian_night that are the same except for the color_temp/rgb_color in the data of the light.turn_on service call.
edit: I’m now going back to this original template for a more condensed configuration.

Your suggestions so far have made my code exponentially better from where it started. And everything is working much snappier and more reliably than my original code. As you may recall that was basically having ~19 automations per entity. One for each time of day.

Also thank you for linking to the original discussion in case someone else is interested in the list-based template.

Ah, I see that now. You’re saying that only this template in the “Circadian Night On Trigger” automation

rgb_color: "{{ states('sensor.cl_color_temp') }}"

will receive this [255, 125, 0] because the automation’s condition ensures it’s after 23:00.

Honestly, I’m a bit surprised that works because Jinja2 templates can only return string values (so rgb_color fails to get a true list, only a string that looks like a list). However, if you say it works, then it works.

Well shoot. Of course you now have me second guessing myself.
Upon testing, guess what?
It doesn’t work.
I guess I hadn’t tested the overnight part of the setup in real life. OR more likely if I did get up in the middle of the night, I didn’t notice. Haha. I guess 500k actually is okay.

Well thanks for setting me straight again.

Will be editing original post and code.

I had my doubts it would work; everything produced by a template is always a string value (even though it may have the appearance of a list).

1 Like

I implemented my colour temperature using the position of the sun.

It works better at higher or lower latitudes and in timezones subject to Daylight savings:

- platform: template
  sensors:
    lights_temp:
      friendly_name: "Lights temperature"
      unit_of_measurement: 'temp'
      value_template: "{{ [400,400-(cos(((-1*[([states.sun.sun.attributes.elevation-15,-90]|max),0]|min|float)/30)*(3.14159265359/2))*289)]|min|int }}"

It produces a nice smooth curve, so you hardly notice the colours of the lights changing.

The brightness changes looks really cool btw! I might steal that one :wink:

3 Likes

I like this… I’ve been thinking of doing something like this as I live in Montana so in the winter the sun doesn’t rise until almost 8a sometimes and is dark by 5p.
In summer the sun is up by 530 sometimes and won’t go set until after 9p. Time based works fairly well but I’ve wanted to try this based on the sun.
I may have to give this a try sometime when my wife is out of town. :laughing:

Why wait? Join the rest of us in the dog house :wink: … and make changes randomly throughout the day while the missus is home.

2 Likes

I already do that haha

Hey @auto.neub. Awesome job with this, thank you. I’ve just implemented it for testing and as I only have four dimmable lights in the house with no color temp adjustments, its pretty straight forward so far.

My question though is how do I do a quick bypass if I just want to turn on a light at a certain brightness level temporarily overriding the circadian automation?

      - service: light.turn_on
        data:
          entity_id: light.lounge_light
          brightness: 30

This always sets the brightness to the circadian value.

So I use an input boolean called input_boolean.circadian_on that as a condition of the automations.

If you want to make a temporary change, turn off the boolean and the lights will stay at your current setting.

Turning off the boolean triggers the automation to create the pre-color scene. I use it for color changes but this also works for your temporary brightness change.

Then when you turn the boolean back on it triggers the pre-color scene

Hope this helps.

Thanks. Was actually quite easy. I just turn off the circadian IB before implementing the custom light brightness then turn it back on.

  - service: input_boolean.turn_off
    entity_id: input_boolean.circadian_on
  - service: light.turn_on
    data:
      entity_id: light.lounge_light
      brightness: 30
  - service: input_boolean.turn_on
    entity_id: input_boolean.circadian_on

Yessir! Glad it worked for you

I like this idea. And started playing with it as soon as I found this. I made some changes.
For triggering every every 30 minutes I am using time pattern:
please not I am using kelvin instead of color_temp as that is what I am used to work with. Also with my lamps it gives better results

- id: circadian_time_transition
  alias: Circadian Time Transition
  initial_state: true
  trigger:
  - platform: time_pattern
    minutes: "/30"
  condition:
  - condition: state
    entity_id: input_boolean.circadian_on
    state: 'on'
  - condition: template
    value_template: >
      {% set lights = ['group.circadian'] %}
      {{ expand(lights)|selectattr('state','eq','on')|list|length > 0 }}
  action:
  - service: light.turn_on
    data_template:
      entity_id: >
        {% set lights = ['group.circadian'] %}
        {{ expand(lights)|selectattr('state','eq','on')
           |map(attribute='entity_id')|join(',') }}
      brightness: "{{ states('sensor.cl_brightness') }}"
      kelvin: "{{ states('sensor.cl_color_temp') }}"
      transition: '10'

Further I made an automation that as soon as you switch on the input_boolean.circadion_on to on, it will update the lamps as well, so you do not have to wait for 30 minutes (or whatever value you are using in the time_pattern).

- id: circadian_time_start
  alias: Circadian Time Start
  initial_state: true
  trigger:
  - platform: state
    entity_id: input_boolean.circadian_on
    from: 'off'
    to: 'on'
  condition:
  - condition: template
    value_template: >
      {% set lights = ['group.circadian'] %}
      {{ expand(lights)|selectattr('state','eq','on')|list|length > 0 }}
  action:
  - service: light.turn_on
    data_template:
      entity_id: >
        {% set lights = ['group.circadian'] %}
        {{ expand(lights)|selectattr('state','eq','on')
           |map(attribute='entity_id')|join(',') }}
      brightness: "{{ states('sensor.cl_brightness') }}"
      kelvin: "{{ states('sensor.cl_color_temp') }}"
      transition: '10'

May I ask what scale this is? mired?
For me it gives something like 111 in the afternoon

The OP is using Mired [https://en.wikipedia.org/wiki/Mired] and thus needs color_temp in its data section. The relation between Mired and Kelvin is simple: Mired = 1000000 / T (with T in Kelvin).

111 mired = 9009 Kelvin
255 mired = 3922 Kelvin
500 mired = 2000 Kelvin

1 Like

Thanks. How do you calculate the color in your setup?

In the same way, but with other values for the color temperatures. Below is what I have right now. I still need to tweak the exact values, but you can get the idea. I get up at 7:00 so then I want to have the highest value for the color temperature (5000), during the day it will be a bit less and after 17:00 (5pm) I want it to slowly get warmer. At around 19:30 (7:30pm) I want it to be at it warmest.

- platform: template
  sensors:
    cl_brightness:
      friendly_name: Circadian Brightness
      value_template: >
        {% set hours = [[0,1],[500,10],[600,55],[630,105],[700,195],[730,220],
                        [800,230],[900,255],
                        [1830,225],[1900,200],[1930,125],[2000,115],
                        [2030,110],[2100,100],[2200,85],[2230,25],[2300,1]] %}
        {% set t = states('sensor.time').replace(':','')|int %}
        {% set ns = namespace(level = 0) %}
        {% for h in hours if t >= h[0] %}
          {% set ns.level = h[1] %}
        {% endfor %}
        {{ ns.level }}

    cl_color_temp:
      friendly_name: 'Circadian Color Temperature'
      value_template: >
        {% set hours = [[0,1800],[600,2500],[630,3500],[645,4500],[700,5000],
                        [800,4500],[1705,4500],[1730,4000],
                        [1800,3500],[1830,3000],[1900,2550],[1930,2000],[2000,1900],
                        [2030,1800]] %}
        {% set t = states('sensor.time').replace(':','')|int %}
        {% set ns = namespace(level = 0) %}
        {% for h in hours if t >= h[0] %}
          {% set ns.level = h[1] %}
        {% endfor %}
        {{ ns.level }}

This I have in my sensors.yaml

1 Like

Can you explain your formula?

Sure - here’s a practical example with plots you can try:

Just make a copy and play with the constants / working.

I’m not a mathematician, so if you find a cleaner way, then I’m all ears!

1 Like

Hey Guys!

Glad to see this has been useful for someone else. I really like the granular control of this.

I actually really like this better and have since switched my config to the time pattern as well. Just a much cleaner code.
Thanks!