Circadian light with Philips Hue, independent from world clocks ;-)

working right now, just have to figure out why the light switches off , or on with rgb/hs settings, in which case the color_temp is not available of course. (believed the circadian automation to do that, but now believe something else is doing it, since the circadian sensor is correct, and now also showing on the light in the frontend)

4433

the thing that might confuse the light is, that this is a color lamp, and color_temp is mutually exclusive for hs_color, or any other color setting. So when any Hue scene is set on this light that actually uses a real color setting this circadian sensor and setting is lost…

why would you only calculate the length of day when the sun is up? if the sun is down, the sensor has no value… For the other sensor, and the automation depending on it, it doesn’t make any difference if the length of day sensor is simply as follows doesn’t it? Since you’re calling the condition of the sun being up/above horizon there also?:

  length_of_day_factor:
    friendly_name: 'Length of day' # factor for circadian light calculation'
    value_template: >
        {% 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)|round(5) }}

just don’t like empty sensors…

Correct, as this is true for any other value which can get overridden as soon as a new automation kicks in. That’s why my automation re-calculates the circadian light every time this automation gets triggered.

This is true. In reality, the “length of day factor” does calculate the length of the NEXT day because I did not find a way to calculate the day-length for the current day. Therefore, as soon as the sun sets (= it’s getting night), the states.sun.sun.attributes.next_setting switches to a point in time which will happen the day after, so the result of the equation gets inverted (negative) and the sensor becomes useless. I would recommend using this sensor only in helper mode, without even being displayed in the GUI.

This is the sensor.farbtemp you’re referring to, and be aware that my mechanism aims at a) displaying circadian light as long as the sun is above horizon, b) then switching to a predefined value during dusk/dawn

and c) goes to a predefined value as soon as it’s neither day, nor dusk, nor dawn.

You can find more information on my personal dusk/dawn calculation here, however it’s not perfect yet: Automations with dusk, dawn, night and day period

So in fact, it goes a little further than what you suggested above and that’s whay I would still need to build in a condition to check whether the sun is above horizon.

thank you! very useful and nice tool. learning both about Hue and mired here. Which is why I started with the Hue light system in the first place, so, again thanks!

for the icons you might be able to use this template I use in another sensor, ive adapted it quickly to your period_of_day:

  icon_template: >-
    {% if (as_timestamp(states.sun.sun.attributes.next_dusk)) - (as_timestamp(states.sun.sun.attributes.next_setting)) < 0 %}
      mdi:weather-sunset-down
    {% elif (as_timestamp(states.sun.sun.attributes.next_rising)) - (as_timestamp(states.sun.sun.attributes.next_dawn)) < 0 %}
      mdi:weather-sunset-up
    {% elif (states.sun.sun.attributes.elevation) < 0 %}
      mdi:weather-night
    {% else %}
      mdi:weather-sunny
    {% endif %}
1 Like

Hi @mastermarkush,

Thank you for your excellent formula, it works spledidly at my home. Could you please provide your calculation sheet also? My wife finds the light too “cold” during noon, so I would like to reduce the peak from 175 mireds to something around 200…

As an idea for all adopters of circadian lights: I defined a Hue scene for each room called “Circadian Light”. I update this scene via REST service to the Hue API every 10 minutes so that it always includes the current color_temp from the sensor. My Hue dimmer switches all use the “Circadian” scenes, so the lights are immediately set to their correct color_temp as soon as I switch them on. In HA, I defined templates switches for the lights (turn_on action activates the “Circadian” scene") so I can facilitate the same logic from HA too.

In addition to that, I also established an automation that updates the color_temp of active lights every 10 minutes. This automation compares the color_temp the light has at the moment with the sensor value. If they do not differ too much (i. e. <15 mired, will probably be adjusted over the next months…), the light is updated. That way I avoid that if I manually activate another scene, it gets overwritten after 10 minutes by the circadian light.

HI @cicero222, thanks for your suggestions and ideas!
would you share your code please, so we could easily adopt and implement into our own settings?

Ive been testing the circadian sensor with several of my lights, and indeed enjoy the results, your bigger setting might be very useful.
thanks,
Marius

REST service to update the HUE scenes:

rest_command:
  set_dynamic_hue_scene:
    url: "http://192.168.1.33/api/<YOUR_API_CODE_HERE>/scenes/{{scene_id}}/lightstates/{{light_id}}"
    method: "put"
    content_type: "application/json"
    payload: '{"on":true,"bri":{{states.sensor.circadian_brightness.state}},"ct":{{states.sensor.circadian_color_temp.state}}'

The service updates both color_temp and brightness for which I have another sensor defined. Unfortunately you have to update each single light within a scene and cannot update the whole scene with one call… :slightly_frowning_face:
Reference: https://developers.meethue.com/documentation/scenes-api#43_modify_scene

This automation kicks in every 10 minutes and sets the color_temp to the up-to-date value:

automation:
  - alias: "Update Hue Circadian Scenes"
    trigger:
      - platform: time
        minutes: "/10"
        seconds: 05
      
    action:
      - service: rest_command.set_dynamic_hue_scene
        data_template:
          scene_id: "mkaM4wCS0zTytUr"
          light_id: 1
      - service: rest_command.set_dynamic_hue_scene
        data_template:
          scene_id: "mkaM4wCS0zTytUr"
          light_id: 2

This automation updates all active lights every 10 minutes with the current color_temp value:

automation:
  - alias: "Update Circadian Lights"
    trigger: 
      - platform: time
        minutes: "/10"
        seconds: 00
      
    action:     
      - service: light.turn_on
        data_template:
          entity_id: >-
            {% for lamp in states.light if lamp.state == 'on' -%}
              {% if lamp.attributes.color_temp %}
                {% set currentvalue = lamp.attributes.color_temp %}
              {% elif lamp.attributes.xy_color %}
                {% set currentvalue = (-449*((lamp.attributes.xy_color[0]-0.332)/(lamp.attributes.xy_color[1]-0.1858))**3)+(3525*((lamp.attributes.xy_color[0]-0.332)/(lamp.attributes.xy_color[1]-0.1858))**2)-(6823.3*((lamp.attributes.xy_color[0]-0.332)/(lamp.attributes.xy_color[1]-0.1858)))+(5520.33) %}
                {% set currentvalue = (1 / currentvalue) * 1000000 %}
              {% else %}
                {% set currentvalue = 0 %}
              {% endif %}
              {% if (currentvalue - states.sensor.circadian_color_temp.state|float)|abs < 15 %}
                {{lamp.entity_id}},
              {% endif %}
            {% endfor -%}
            light.dummy
          color_temp: "{{ states.sensor.circadian_color_temp.state }}"
          transition: 3

As also other users have noticed, Hue lights may have either a color_temp attribute in HA or an xy color attribute, depending on the last action performed on them. So I have to check which one is defined. If it is xy, I have to convert it to Kelvin and then to mired. I found one formula here (although there seem to be also others): https://developers.meethue.com/comment/3046#comment-3046

Combining all lamps whose current values do not differ too much from the sensor value into one list is just beyond my programming skills, so I compose a list that always contains one dummy light. I would really appreciate if someone could show me a cleaner way to handle this (and also cater with the case that no light at all shall be updated)!

3 Likes

That’s easy. In this case, the just modify the value 175 to 200 in the sensor.colortemp at the end of the formula (see above).

No, it’s not. That way the entire parabola would move up and would not deliver 350 mireds at the beginning/end of the day cycle but 375. I would need to change the u-factor in your formula. But anyway, for the moment it’s fine…

[Edit]: Just saw your PM, thanks a lot for the file!

You’re right. I forgot about the dependencies. You’ll see the calculation file and understand how to set it up correctly.

hi @mastermarkush

would you recognize this error:

2018-07-20 09:50:24 ERROR (MainThread) [homeassistant.helpers.entity] Update for sensor.circadian_light fails
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 196, in async_update_ha_state
    yield from self.async_device_update()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 317, in async_device_update
    yield from self.async_update()
  File "/usr/local/lib/python3.6/asyncio/coroutines.py", line 212, in coro
    res = func(*args, **kw)
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/sensor/template.py", line 183, in async_update
    self._state = self._template.async_render()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/template.py", line 132, in async_render
    return self._compiled.render(kwargs).strip()
  File "/usr/local/lib/python3.6/site-packages/jinja2/asyncsupport.py", line 76, in render
    return original_render(self, *args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/usr/local/lib/python3.6/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "<template>", line 3, in top-level template code
TypeError: can't multiply sequence by non-int of type 'float' 

which change would you suggest to mitigate the error in these 2 sensors:

  - platform: template
    sensors:
      length_of_day_factor:
        friendly_name: 'Length of day' # factor for circadian light calculation'
        value_template: >
            {% 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)|round(5) }}

      circadian_light:
        friendly_name: 'Circadian light'
        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 %}

not sure if the error is caused by the HA startup sequence not having set the value correctly yet, or the template is not correct?

Thx for having another check,
Marius

Hi @Mariusthvdb,
I have also seen this error in my config from time to time. It refers to the sensor.circadian_light which cannot be calculated. From what I see, the template itself is ok, I concluded that this might be due to a) either a point of time where the values couldn’t be calculated e.g. during startup or b) during dusk periods when - even if only for a short moment - the circadian light sensor calculates a value (sun = above horizon), the daylength sensor however provides no value.

This might be because of the way I calculate the length of the day: based on the NEXT day. Since NEXT days are either getting longer or shorter compared to today, this might create some tiny hick-ups.

getting back to this, Id propose this change in the value_template:

      {% if is_state('sun.sun' , 'above_horizon') %}
        {{ ((states('sensor.length_of_day_factor') | default(0) | round (5)) * 
           ((state_attr('sun.sun','azimuth'))-180)**2 + 175) | round }}

It creates a startup value, which prevents the error mentioned in my post above and changes the |int to round.

error gone.

I dont understand yet why the round(5) is needed both in the sensor.length_of_day and in the template using the state of that sensor. Seems to be superfluous?
I took the round(5) out of the circadian_light sensor, but that does not work…?

---- edit----

well, that was a one timer i think, the error came back. Ive now changed it to:

    {{ ((states('sensor.length_of_day_factor') | round (5)) * 
       ((state_attr('sun.sun','azimuth') )-180)**2 + 175) | round }}

so at least they’re both floats. No error this time, lets hope it stays that way

Thanks,
Marius

Hi Marius, I just came across your project and want to implement it in our dining and living room. Could you send me the Calc sheet so I can tweek the settings a bit?

Best,
Reinhard

I would also love a copy of your Excel work. I’m working on a native Node-RED pallet that duplicates this functionality and picking up from your work would be so incredibly helpful!

1 Like

@mastermarkush Would love to get a look into the file as well!

@mastermarkush, I would love to see your Excel file, as well. Could you maybe create a gist with the file and share the link?

You can download the mired calculation file in my github repository: https://github.com/mastermarkush/homeassistant

1 Like

Hi, thank you for sharing this!
I integrated your sensors in my config and will check the graphs in the following days to see if the constants need some adjusting.
Anyway, trying to avoid the errors on startup, I also modified the colortemp template as per recommendations and so far no errors popped up:

    colortemp:
      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') | round (5)) * ((state_attr('sun.sun','azimuth') )-180)**2 + 175) | int }}
          {% elif ((as_timestamp(state_attr('sun.sun','next_dusk'))) - (as_timestamp(state_attr('sun.sun','next_setting'))) < 0) or ((as_timestamp(state_attr('sun.sun','next_rising'))) - (as_timestamp(state_attr('sun.sun','next_dawn'))) < 0) %}
            390
          {% else %}
            500
          {% endif %}

Best,
R

Is it possible to have multiple zones of lights set this or do they called by the single instance? I have the switch loaded in my office, but was thinking it would be nice for this to work in my kitchen or bedroom as well.

Hi @atmasphere! I think I didn’t get your question right. What I understood is that you want to have multiple (independent) lights apply the mechanics of adaptive “circadian” light. If so, then simply apply the colortemp variable to the color_temp in the data_template for the specific light or group of lights that you turn on… Maybe you can reformulate your question?