Terrarium lighting variables

Hi all

I want to turn on and off the lights of my terrarium based on some calculations.

On: time of sunset
Off: time of sunset + duration

I want to calculate the duration based on the number of the day in the year. My reference day is 21/12 = day 355. This is the middle of winter → duration = 720 minutes (12 hours of light). Every day that is more or less than this day needs an additional 1.32 minutes of light.
Example 20/12 and 22/12 are 1 day different → duration is 720 + 1.32 = 721.32 minutes.
I simulated this calculation in Excel, where the formulas work. Now I need to apply that logic in HASS and that is the difficult part for me.

I started by creating 3 helpers in Settings - Helpers:

  • template sensor to save the ‘Lights On’ time = {{ states(‘sensor.sun_next_rising’) }}
  • template sensor to save the ‘Lights Off’ time = {{ states(‘sensor.sun_next_rising’) + timedelta(minutes=duration) }}
  • number = 720 (static value for now)

The first issue I have is that I can’t seem to add the duration to the On Time. Even {{ states(‘sensor.sun_next_rising’) + timedelta(minutes=720) }} is not working (sensor unavailable).

What am I doing now?

When this is working I want to calculate the duration.

Any feedback and tips are welcome!

There must be a date you have in mind, perhaps June 21, where the calculation switches from computing the number of days after December 21 to before December 21. What’s that date?

For example, given a date like June 28, do you calculate the number of days after or before December 21?

You have to convert a datetime string into a datetime object if you want to add it to a timedelta object.

{{ states('sensor.sun_next_rising') | as_datetime + timedelta(minutes=720) }}

Correct, 21/06 is the switching day. All days above 180 must be substracted again.
22/06 = 179 days difference
21/06 = 180 days difference
20/06 = 181 days difference = 179 days difference

The convertion to datetime works perfect. Thanks for that!

Copy-paste the following template into the Template Editor. Change the value of the d variable to different dates and confirm it reports the expected number of days.

{% set d = '2024-12-23T00:00' | as_datetime | as_local %}
{% set y = d.year %}
{% set (y1,y2) = iif(d.month == 12 and d.day >= 21, (y, y+1), (y-1, y)) %}
{% set r1 = ('%d-12-21T00:00' % y1) | as_datetime | as_local %}
{% set r2 = ('%d-12-21T00:00' % y2) | as_datetime | as_local %}
{% set mid = ('%d-06-21T00:00' % y2) | as_datetime | as_local %}
{% set diff = iif(d <= mid, d - r1, r2 - d) %}
{{ diff.days }}

If it works to your satisfaction, use the following version which employs the current date and multiplies the number of days by 1.32.

{% set d = now() %}
{% set y = d.year %}
{% set (y1,y2) = iif(d.month == 12 and d.day >= 21, (y, y+1), (y-1, y)) %}
{% set r1 = ('%d-12-21T00:00' % y1) | as_datetime | as_local %}
{% set r2 = ('%d-12-21T00:00' % y2) | as_datetime | as_local %}
{% set mid = ('%d-06-21T00:00' % y2) | as_datetime | as_local %}
{% set diff = iif(d <= mid, d - r1, r2 - d) %}
{{ (diff.days * 1.32) | round(2) }}

I suggest using the template in a Template Sensor helper.

Would it be easier to use a timer combined with the sunrise time?

For example, automation 1 triggers at sunset. At that time, you compute how much time has elapsed since sunrise. If it’s been less than 12 hours, subtract the time from 12 hours and set a timer for that period of time. Then automation 2 triggers when the timer fires and simply turns the light off without needing to do any logic.

Automation 1 won’t set a timer if there has been 12 hours of light or more at sunset, so automation 2 just won’t fire on those days.

Although it’s two automations, it feels a little cleaner, in part because you don’t have to worry about which phase of the year you’re in to do the right calculation for the duration.

Any progress to report?

Yes. I’ve written the folowing code to calculate the duration.
Apparantly the template syntax is Jinja python. Which is mentioned in the developper tools - template editor…

{% set day_current = now().strftime('%j') | int %}

{% set day_winter = 355 %} {# 21/12 = day 355 #}
{% set day_summer = 172 %} {# 21/06 = day 172 #} 
{# half year = 183 days #}
{% set days_half_year = day_winter - day_summer %}

{% set days_offset = (day_winter - day_current) | abs %}

{# offset can't be larger than 183 days #}
{# 20/06 - offset = 182 #}
{# 21/06 - offset = 183 #}
{# 22/06 - offset = 182 #}
{% if days_offset > days_half_year %}
  {% set days_offset = days_half_year - (days_offset - days_half_year) %}
{% endif %}

{% set light_winter = 720 %} {# 12 hours #}
{% set light_summer = 960 %} {# 16 hours #}
{% set light_diff = light_summer - light_winter %}
{% set light_diff_day = light_diff / days_half_year %}

{{ (light_winter + (days_offset * light_diff_day)) | int }}

Next question I have is how to change the layout from this sensor on a dashboard from a textfield to a label field (similar as the fields above)?

If you’re trying to match day length of a different locale, another option would be to use the Sun2 integration’s “Daylight” sensor option.

That is actually a good idea! :slight_smile:
I’ll keep it in mind, might be useful.

Edit: seasons in Belgium don’t allign with the seasons in Australia. Winter here is summer there. So an offset of half a year is needed to make it work like that.

Couldn’t you just invert the latitude or use the antipodes?

What was wrong with what I suggested a week ago? It computes the correct number of days and all you had to do was add it to 720.

I tested your code in the Template Editor and the value it produced today is 801. That’s the same value produced by my example (if you round it up; at the time I didn’t know if you wanted to round the value).

I had tested my version with various dates, and years, and it produced correct results. It also handles leap years correctly so it’s slightly more accurate than your version which makes the mistaken assumption that June 21 and December 21 are always 172 and 355 days.

{% set (d, y) = (now(), now().year) %}
{% set (y1,y2) = iif(d.month == 12 and d.day >= 21, (y, y+1), (y-1, y)) %}
{% set r1 = ('%d-12-21T00:00' % y1) | as_datetime | as_local %}
{% set r2 = ('%d-12-21T00:00' % y2) | as_datetime | as_local %}
{% set mid = ('%d-06-21T00:00' % y2) | as_datetime | as_local %}
{% set diff = iif(d <= mid, d - r1, r2 - d) %}
{{ (720 + (diff.days * 1.32)) | round(0) }}

There is probably nothing wrong with your code. And I appreciate it. I just had written my code before you posted yours. The convertion to datetime got me moving again. Sorry, should have reported it earlier!

Edit: I ignored the leap years because my bearded dragon won’t notice 1 day every 4 years with a minute more or less light.

Any advice on how to change the textfield to a label?

This is how my dashboard looks like now.
Almost done. Some tweaking on the bottom graph. And missing a function to change the color of an entity in the graph. Hot zone in blue makes no sense :slight_smile:

Next question I have is how to change the layout from this sensor on a dashboard from a textfield to a label field (similar as the fields above)?

You can set an input_number or input_text with the “simple-entity” type and it will show as either text or number without the obvious ability to edit it. You can still edit it if you click on it though. You need to use the Code Editor mode to do this and it does display an error that the Visual Editor is not supported, but it works!

type: entities
entities:
  - entity: input_number.rainfall_today
    type: simple-entity
  - entity: input_number.rainfall_last_7_days
    type: simple-entity
title: Rainfall Last 7 Days

Unfortunately I no longer have free time for your project. Good luck!

Thanks! Exactly what I needed.

No problem, thanks for your previous feedback though.

Dashboard is as good as finished. Happy with it. Thanks again for the replies. Just a few more questions to optimize it a little more.

  1. Can I remove the swith icon next to Lights in the header? I was able to remove it for both UV Light and Heat Light by using ‘type: simple-entity’, but that does not seem to work for the switch icon above.

  2. I did some research and is does not seem possible to keep a (rolling) minimum and maximum value ofa certain sensor. Is that correct. It would be useful to know the minimum and maximum temperature of the last week for example of each of the 3 sensors.