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

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?

think you understand but to be clear, I’d like to have a switch for multiple rooms.

Office lights are currently utlizing this (though it broke for some reason). I was thinking I’d like to turn it on for my kitchen and bedroom as well but want the ability to turn the toggle on and off by room.

broken switch in place but this is my office view -

Screenshot_10

Dear @mastermarkush,

could I use this to automate my window roller shutters with this component? If yes, would you give a short hint, how?

What I’d like have is a little fixed time delay, so not to close the shutters instantly as dawn kicks in, but like 30 mins later or so…

many thanks in advance

Hi John,
for this use case I’d suggest you to have a look at my other project which relates more to automations around day periods.

Cheers,
Markus

Hi @mastermarkush
Would it be possible for you to send me your mired calculation file? I looked at your github site, but looks like it’s no longer there.
Really cool work.
Thank you.
Mark