Stuck with Lambda Time and Sun calculations

I can’t code. I used ChatGPT for everything and tidied up all the errors I could.

For a chicken coop I need the ESPhome device (kc868-a8) handle the automations separately from HASS because I don’t want chickens do die because my Pi is not booting. I do want HASS to keep an eye on the ESPhome device.

For time is use DS3231 RTC (DS1307 in config) together with the SNTP platform.

GPT decided to use intervals and lambdas for control of the following:

  • Gate needs to be open from sunrise till sunset if it is not too cold outside
  • Lamp needs to be on from sunrise till 14 hours (user set) after sunrise.
  • Heater needs to be on when it is cold.

but it gets really stuck on row 189 where you need to know:

  • What time it is?
  • When is sunrise and sunset?
  • Are the automations at correct states for this time?
esphome:
  name: kc868-a8
  platform: ESP32
  board: esp32dev

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO17_OUT
  phy_addr: 0

i2c:
  sda: 4
  scl: 5
  scan: true
  
pcf8574:
   - id: 'pcf8574_hub_out_1'  # for output channel 1-8
     address: 0x24

   - id: 'pcf8574_hub_in_1'  # for input channel 1-8
     address: 0x22

time:
  - platform: sntp 
    id: sntp_time
    servers:
      - pool.ntp.org
    on_time:
      - seconds: 0
        minutes: 0
        hours: 2
        days_of_week: MON-SUN
        then:
          - lambda: |-
              ESPTime time = id(sntp_time).now();
              id(my_rtc).write_time();

  - platform: ds1307
    id: my_rtc
    update_interval: 60s

sensor:
  - platform: dallas_temp
    name: "Outside Temperature"
    id: outside_temperature
    accuracy_decimals: 1
    one_wire_id: bus1

  - platform: dallas_temp
    name: "Indoor Temperature"
    id: indoor_temperature
    accuracy_decimals: 1
    one_wire_id: bus2

  - platform: homeassistant
    id: lamp_duration_input
    entity_id: input_number.lamp_duration
    on_value:
      then:
        - lambda: |-
            id(lamp_duration) = (int) id(lamp_duration_input).state;

binary_sensor:
  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 0
      mode: INPUT
      inverted: true
    id: gate_open
    name: "Gate Open"

  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 1
      mode: INPUT
      inverted: true
    id: gate_closed
    name: "Gate Closed"

  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_in_1
      number: 2
      mode: INPUT
      inverted: true
    id: door_open
    name: "Door Open"

  - platform: homeassistant
    id: gate_automation
    entity_id: input_boolean.gate_automation
    on_state:
      then:
        - lambda: |-
            id(gate_automation_enabled) = id(gate_automation).state;
  - platform: homeassistant
    id: lamp_automation
    entity_id: input_boolean.lamp_automation
    on_state:
      then:
        - lambda: |-
            id(lamp_automation_enabled) = id(lamp_automation).state;
  - platform: homeassistant
    id: heater_automation
    entity_id: input_boolean.heater_automation
    on_state:
      then:
        - lambda: |-
            id(heater_automation_enabled) = id(heater_automation).state;

switch:
  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 0
      mode: OUTPUT
      inverted: true
    id: gate_rise
    name: "Gate Rise"

  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 1
      mode: OUTPUT
      inverted: true
    id: gate_close
    name: "Gate Close"

  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 2
      mode: OUTPUT
      inverted: true
    id: lamp
    name: "Lamp"

  - platform: gpio
    pin:
      pcf8574: pcf8574_hub_out_1
      number: 3
      mode: OUTPUT
      inverted: true
    id: heater
    name: "Heater"

globals:
  - id: gate_automation_enabled
    type: bool
    restore_value: yes
    initial_value: 'true'

  - id: lamp_automation_enabled
    type: bool
    restore_value: yes
    initial_value: 'true'

  - id: heater_automation_enabled
    type: bool
    restore_value: yes
    initial_value: 'true'

  - id: lamp_duration
    type: int
    restore_value: yes
    initial_value: '14'

sun:
  latitude: 59
  longitude: 24
  id: sunc

interval:
  - interval: 1min
    then:
      - lambda: |-
          // Get current time from RTC
          auto time = id(my_rtc).now();

          // Calculate sunrise and sunset times
          auto sun = id(sunc);
          // auto sunrise = sun->sunrise().timestamp;
          // auto sunset = sun->sunset().timestamp;
          auto sunrise = sun->sunrise(time, 0.0).value();
          auto sunset = sun->sunset(time, 0.0).value();
          auto lamp_off_time = sunrise + id(lamp_duration) * 3600;

          // Check if it's between sunrise and sunset
          if (time.timestamp > sunrise && time.timestamp < sunset) {
            // Gate automation check
            if (id(gate_automation_enabled) && id(outside_temperature).state > 0) {
              if (!id(gate_open).state) {
                id(gate_rise).turn_on();
                while (!id(gate_open).state) {
                  delay(100);  // Wait until the gate is open
                }
                id(gate_rise).turn_off();
              }
            }
          }

          // Check if it's between sunrise and lamp_off_time
          if (time.timestamp > sunrise && time.timestamp < lamp_off_time) {
            // Lamp automation check
            if (id(lamp_automation_enabled) && !id(lamp).state) {
              id(lamp).turn_on();
            }
          } else {
            // Turn off the lamp after lamp_off_time
            if (id(lamp).state) {
              id(lamp).turn_off();
            }
          }
  - interval: 30s
    then:
      - if:
          condition:
            and:
              - lambda: 'return id(heater_automation_enabled);'
              - lambda: 'return id(indoor_temperature).state < 2;'
          then:
            - switch.turn_on: heater
      - if:
          condition:
            lambda: 'return id(indoor_temperature).state >= 5;'
          then:
            - switch.turn_off: heater

one_wire:
  - platform: gpio
    pin: GPIO14
    id: bus1
  - platform: gpio
    pin: GPIO13
    id: bus2

api:
    encryption:
      key: "secret"

Thanks for your time!

You want someone to correct chatgpt’s errors? Nah…

No not errors per se. Help me with calculating time as I struggle to understand from documentation how to get compatible values from both Sun and Time for Lambda logic.

Perhaps you’re correct that I’m lazy and hoped I don’t have to learn the correct terminology and some C++ to make the jump from YAML to writing actual code.

I also really like making ESP32 devices that work with no other required devices or servers.

ESPhome has some nice built in code for sunrise and sunset. This is code I use to turn on a light at sunset then turn it off at midnight. No Lamda required.

-3.5 turns on the light when the sun is 3.5 degrees below the horizon. This is the definition of “sunset” :slight_smile:

sun:
  latitude: !secret latitude
  longitude: !secret longitude

  on_sunset:
    - elevation: -3.5°
      then:
        - switch.turn_on:
            id: relay

time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:  # Turn off at midnight
      - seconds: 0
        minutes: 0
        hours: 0
        then:
          - switch.turn_off: relay

Thats where I started from but on_sunrise for example doesn’t seem to trigger when the device has somehow (power loss) missed that moment. With Interval, Lambda and RTC I hope to have a setup that is still budget friendly but more resilient.

Me too, I don’t even have home assistant setup. For my curiosity, why homeassistant time instead of sntp time?

Read this a bit more. There is a sun.is_above_horizon test condition. If you loose power a lot then check on boot to see if the sun is up or not and act accordingly.

No reason really. I guess I just want to keep traffic down on the local SNTP server. For better reliability I would use a DS3231 RTC or check the time using a GPS modem or both!

Only? No rubidium atomic oscillator for pet feeder?? :joy:

Actually it was just some bad codding on my part. I’m using a athom smart plug (very nice device) and the default config already had SNTP configured. I changed my config to use SNTP only. It has an interesting setting to be “nice” to the SNTP servers.

  # Change sync interval from default 5min to 6 hours
    update_interval: 360min 

I didn’t really think about sun.is_above_horizon because it only works for the gate which operates in those conditions.
The complicated thing (for me) is having the lights run from sunrise till sunrise + 14 hours. So for example when the system powers on 2 hours after sunrise the lights would stay on for 12 hours.

Why is that complicated? Tmed lights/smart lights are one of the simplest things to do.

Well normally yes but like I said in the first post that I want something more resilient so there is less chance of chicken dying because HA is not working.

I will mark this question as answered now as I think the hardest part is done:

  1. getting sunrise and sunset time for the current day:
          ESPTime start_of_day;
          start_of_day.is_dst = rtc_time.is_dst;
          start_of_day.year = rtc_time.year;
          start_of_day.month = rtc_time.month;
          start_of_day.day_of_year = rtc_time.day_of_year;
          start_of_day.day_of_month = rtc_time.day_of_month;
          start_of_day.day_of_week = rtc_time.day_of_week;
          start_of_day.hour = 0;
          start_of_day.minute = 0;
          start_of_day.second = 0;

          auto sunrise = id(my_sun).sunrise(start_of_day, -0.833);
          auto sunset = id(my_sun).sunset(start_of_day, -0.833);
  1. Calculate the right time to turn off the lamp:
if (sunrise.has_value() && sunset.has_value()) {
            auto sunrise_time = sunrise.value();
            auto sunset_time = sunset.value();

            // Convert ESPTime to Unix timestamp
            uint32_t sunrise_epoch = sunrise_time.timestamp;
            uint32_t sunset_epoch = sunset_time.timestamp
            uint32_t rtc_epoch = rtc_time.timestamp;

            // Calculate when to turn off the lamp based on lamp_duration input in hours
            uint32_t lamp_duration_seconds = id(lamp_duration) * 3600;
            uint32_t lamp_off_epoch = sunrise_epoch + lamp_duration_seconds;

Now I can operate my coop in a way that I don’t need HA nor internet and it will still work if there are power outages with:

if (rtc_epoch >= sunrise_epoch && rtc_epoch <= sunset_epoch)  { ...

which is unremarkable because it’s basically sun.is_above_horizon what andrew_NH suggested but it didn’t help me with this:

if (rtc_epoch >= sunrise_epoch && rtc_epoch <= lamp_off_epoch) { ...

I mean sure chicken won’t die because its dark or the gate remained closed but it can be dangerous if the gate was left open for the night.

Maybe this is overkill and certainly hoped I never had to learn what C++ is but here we are :smiley:

1 Like

Hello and sorry that i jump into this…

So the code u r showing…is the time from hass needed or can i just open/close something on lets say on_sunrise → open and on_sunset → close?
And is there a good guess value on what the elevation would be in terms of before and after sun rise/set?
Is -3.5 ten minutes or 30?
I want to have my chickens to be 30 min after sunrise and 15 min after sunset opened and closed…and i am looking for a solution that is not in has…but preferred with some kind of feedback if it actually happened or not…

Then sun component calculates where the sun is at a given time so you need to supply accurate time either manually, from hass, ntp server or rtc after every boot.

1 degree of sun angle can be 5 minutes near the equator and it can be hours near the poles :smiley:

You could use a calculation like I used my lamp but add another time variable to offset the sunrise or sunset. Hass has history so you can see when the switches actually toggled but if there is no constant connection then I would write a timestamp into a global when the action triggers.